設計模式 | 適配器模式及典型應用

前言

本文介紹適配器模式,源碼分析spring aop, jpa, mvc中的適配器模式java

推薦閱讀

設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
設計模式 | 裝飾者模式及典型應用spring

更多內容可訪問個人我的博客:laijianfeng.orgsql

關注【小旋鋒】微信公衆號,及時接收博文推送數據庫

關注【小旋鋒】微信公衆號

適配器模式

適配器模式(Adapter Pattern):將一個接口轉換成客戶但願的另外一個接口,使接口不兼容的那些類能夠一塊兒工做,其別名爲包裝器(Wrapper)。適配器模式既能夠做爲類結構型模式,也能夠做爲對象結構型模式。設計模式

在適配器模式中,咱們經過增長一個新的適配器類來解決接口不兼容的問題,使得本來沒有任何關係的類能夠協同工做。bash

根據適配器類與適配者類的關係不一樣,適配器模式可分爲對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關聯關係;在類適配器模式中,適配器與適配者之間是繼承(或實現)關係。微信

角色

Target(目標抽象類):目標抽象類定義客戶所需接口,能夠是一個抽象類或接口,也能夠是具體類。mvc

Adapter(適配器類):適配器能夠調用另外一個接口,做爲一個轉換器,對Adaptee和Target進行適配,適配器類是適配器模式的核心,在對象適配器中,它經過繼承Target並關聯一個Adaptee對象使兩者產生聯繫。app

Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經存在的接口,這個接口須要適配,適配者類通常是一個具體類,包含了客戶但願使用的業務方法,在某些狀況下可能沒有適配者類的源代碼。框架

缺省適配器模式(Default Adapter Pattern):當不須要實現一個接口所提供的全部方法時,可先設計一個抽象類實現該接口,併爲接口中每一個方法提供一個默認實現(空方法),那麼該抽象類的子類能夠選擇性地覆蓋父類的某些方法來實現需求,它適用於不想使用一個接口中的全部方法的狀況,又稱爲單接口適配器模式。缺省適配器模式是適配器模式的一種變體,其應用也較爲普遍。在JDK類庫的事件處理包java.awt.event中普遍使用了缺省適配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。

示例

類適配器

首先有一個已存在的將被適配的類

public class Adaptee {
    public void adapteeRequest() {
        System.out.println("被適配者的方法");
    }
}
複製代碼

定義一個目標接口

public interface Target {
    void request();
}
複製代碼

怎麼才能夠在目標接口中的 request() 調用 AdapteeadapteeRequest() 方法呢?

若是直接實現 Target 是不行的

public class ConcreteTarget implements Target {
    @Override
    public void request() {
        System.out.println("concreteTarget目標方法");
    }
}
複製代碼

若是經過一個適配器類,實現 Target 接口,同時繼承了 Adaptee 類,而後在實現的 request() 方法中調用父類的 adapteeRequest() 便可實現

public class Adapter extends Adaptee implements Target{
    @Override
    public void request() {
        //...一些操做...
        super.adapteeRequest();
        //...一些操做...
    }
}
複製代碼

咱們來測試一下

public class Test {
    public static void main(String[] args) {
        Target target = new ConcreteTarget();
        target.request();

        Target adapterTarget = new Adapter();
        adapterTarget.request();
    }
}
複製代碼

輸出

concreteTarget目標方法
被適配者的方法
複製代碼

類適配器模式類圖

這樣咱們便可在新接口 Target 中適配舊的接口或類

對象適配器

對象適配器與類適配器不一樣之處在於,類適配器經過繼承來完成適配,對象適配器則是經過關聯來完成,這裏稍微修改一下 Adapter 類便可將轉變爲對象適配器

public class Adapter implements Target{
    // 適配者是對象適配器的一個屬性
    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {
        //...
        adaptee.adapteeRequest();
        //...
    }
}
複製代碼

對象適配器模式類圖

注意這裏的 Adapter 是將 Adaptee 做爲一個成員屬性,而不是繼承它

電壓適配器

再來一個好理解的例子,咱們國家的民用電都是 220V,日本是 110V,而咱們的手機充電通常須要 5V,這時候要充電,就須要一個電壓適配器,將 220V 或者 100V 的輸入電壓變換爲 5V 輸出

定義輸出交流電接口,輸出220V交流電類和輸出110V交流電類

public interface AC {
    int outputAC();
}

public class AC110 implements AC {
    public final int output = 110;

    @Override
    public int outputAC() {
        return output;
    }
}

public class AC220 implements AC {
    public final int output = 220;

    @Override
    public int outputAC() {
        return output;
    }
}
複製代碼

適配器接口,其中 support() 方法用於檢查輸入的電壓是否與適配器匹配,outputDC5V() 方法則用於將輸入的電壓變換爲 5V 後輸出

public interface DC5Adapter {
    boolean support(AC ac);

    int outputDC5V(AC ac);
}
複製代碼

實現中國變壓適配器和日本變壓適配器

public class ChinaPowerAdapter implements DC5Adapter {
    public static final int voltage = 220;
    
    @Override
    public boolean support(AC ac) {
        return (voltage == ac.outputAC());
    }
    
    @Override
    public int outputDC5V(AC ac) {
        int adapterInput = ac.outputAC();
        //變壓器...
        int adapterOutput = adapterInput / 44;
        System.out.println("使用ChinaPowerAdapter變壓適配器,輸入AC:" + adapterInput + "V" + ",輸出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}

public class JapanPowerAdapter implements DC5Adapter {
    public static final int voltage = 110;

    @Override
    public boolean support(AC ac) {
        return (voltage == ac.outputAC());
    }

    @Override
    public int outputDC5V(AC ac) {
        int adapterInput = ac.outputAC();
        //變壓器...
        int adapterOutput = adapterInput / 22;
        System.out.println("使用JapanPowerAdapter變壓適配器,輸入AC:" + adapterInput + "V" + ",輸出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}
複製代碼

測試,準備中國變壓適配器和日本變壓適配器各一個,定義一個方法能夠根據電壓找到合適的變壓器,而後進行測試

public class Test {
    private List<DC5Adapter> adapters = new LinkedList<DC5Adapter>();

    public Test() {
        this.adapters.add(new ChinaPowerAdapter());
        this.adapters.add(new JapanPowerAdapter());
    }

    // 根據電壓找合適的變壓器
    public DC5Adapter getPowerAdapter(AC ac) {
        DC5Adapter adapter = null;
        for (DC5Adapter ad : this.adapters) {
            if (ad.support(ac)) {
                adapter = ad;
                break;
            }
        }
        if (adapter == null){
            throw new  IllegalArgumentException("沒有找到合適的變壓適配器");
        }
        return adapter;
    }

    public static void main(String[] args) {
        Test test = new Test();
        AC chinaAC = new AC220();
        DC5Adapter adapter = test.getPowerAdapter(chinaAC);
        adapter.outputDC5V(chinaAC);

        // 去日本旅遊,電壓是 110V
        AC japanAC = new AC110();
        adapter = test.getPowerAdapter(japanAC);
        adapter.outputDC5V(japanAC);
    }
}
複製代碼

輸出

使用ChinaPowerAdapter變壓適配器,輸入AC:220V,輸出DC:5V
使用JapanPowerAdapter變壓適配器,輸入AC:110V,輸出DC:5V
複製代碼

適配器模式總結

主要優勢

  1. 將目標類和適配者類解耦,經過引入一個適配器類來重用現有的適配者類,無須修改原有結構。
  2. 增長了類的透明性和複用性,將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,並且提升了適配者的複用性,同一個適配者類能夠在多個不一樣的系統中複用。
  3. 靈活性和擴展性都很是好,經過使用配置文件,能夠很方便地更換適配器,也能夠在不修改原有代碼的基礎上增長新的適配器類,徹底符合「開閉原則」。

具體來講,類適配器模式還有以下優勢:

  • 因爲適配器類是適配者類的子類,所以能夠在適配器類中置換一些適配者的方法,使得適配器的靈活性更強。

對象適配器模式還有以下優勢:

  • 一個對象適配器能夠把多個不一樣的適配者適配到同一個目標;
  • 能夠適配一個適配者的子類,因爲適配器和適配者之間是關聯關係,根據「里氏代換原則」,適配者的子類也可經過該適配器進行適配。

類適配器模式的缺點以下:

  1. 對於Java、C#等不支持多重類繼承的語言,一次最多隻能適配一個適配者類,不能同時適配多個適配者;
  2. 適配者類不能爲最終類,如在Java中不能爲final類,C#中不能爲sealed類;
  3. 在Java、C#等語言中,類適配器模式中的目標抽象類只能爲接口,不能爲類,其使用有必定的侷限性。

對象適配器模式的缺點以下:

  • 與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。若是必定要置換掉適配者類的一個或多個方法,能夠先作一個適配者類的子類,將適配者類的方法置換掉,而後再把適配者類的子類當作真正的適配者進行適配,實現過程較爲複雜。

適用場景

  • 系統須要使用一些現有的類,而這些類的接口(如方法名)不符合系統的須要,甚至沒有這些類的源代碼。
  • 想建立一個能夠重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在未來引進的類一塊兒工做。

源碼分析適配器模式的典型應用

spring AOP中的適配器模式

在Spring的Aop中,使用的 Advice(通知) 來加強被代理類的功能。

Advice的類型有:MethodBeforeAdviceAfterReturningAdviceThrowsAdvice

在每一個類型 Advice 都有對應的攔截器,MethodBeforeAdviceInterceptorAfterReturningAdviceInterceptorThrowsAdviceInterceptor

Spring須要將每一個 Advice 都封裝成對應的攔截器類型,返回給容器,因此須要使用適配器模式對 Advice 進行轉換

三個適配者類 Adaptee 以下:

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;
}

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable;
}

public interface ThrowsAdvice extends AfterAdvice {
}
複製代碼

目標接口 Target,有兩個方法,一個判斷 Advice 類型是否匹配,一個是工廠方法,建立對應類型的 Advice 對應的攔截器

public interface AdvisorAdapter {
    boolean supportsAdvice(Advice var1);

    MethodInterceptor getInterceptor(Advisor var1);
}
複製代碼

三個適配器類 Adapter 分別以下,注意其中的 Advice、Adapter、Interceptor之間的對應關係

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}
}

@SuppressWarnings("serial")
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof AfterReturningAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
		return new AfterReturningAdviceInterceptor(advice);
	}
}

class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof ThrowsAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		return new ThrowsAdviceInterceptor(advisor.getAdvice());
	}
}
複製代碼

客戶端 DefaultAdvisorAdapterRegistry

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
    private final List<AdvisorAdapter> adapters = new ArrayList(3);

    public DefaultAdvisorAdapterRegistry() {
        // 這裏註冊了適配器
        this.registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
        this.registerAdvisorAdapter(new AfterReturningAdviceAdapter());
        this.registerAdvisorAdapter(new ThrowsAdviceAdapter());
    }
    
    public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
        List<MethodInterceptor> interceptors = new ArrayList(3);
        Advice advice = advisor.getAdvice();
        if (advice instanceof MethodInterceptor) {
            interceptors.add((MethodInterceptor)advice);
        }

        Iterator var4 = this.adapters.iterator();

        while(var4.hasNext()) {
            AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
            if (adapter.supportsAdvice(advice)) {   // 這裏調用適配器方法
                interceptors.add(adapter.getInterceptor(advisor));  // 這裏調用適配器方法
            }
        }

        if (interceptors.isEmpty()) {
            throw new UnknownAdviceTypeException(advisor.getAdvice());
        } else {
            return (MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);
        }
    }
    // ...省略...
}    
複製代碼

這裏看 while 循環裏,逐個取出註冊的適配器,調用 supportsAdvice() 方法來判斷 Advice 對應的類型,而後調用 getInterceptor() 建立對應類型的攔截器

spring aop 適配器模式

這裏應該屬於對象適配器模式,關鍵字 instanceof 可當作是 Advice 的方法,不過這裏的 Advice 對象是從外部傳進來,而不是成員屬性

spring JPA中的適配器模式

在Spring的ORM包中,對於JPA的支持也是採用了適配器模式,首先定義了一個接口的 JpaVendorAdapter,而後不一樣的持久層框架都實現此接口。

jpaVendorAdapter:用於設置實現廠商JPA實現的特定屬性,如設置Hibernate的是否自動生成DDL的屬性generateDdl;這些屬性是廠商特定的,所以最好在這裏設置;目前Spring提供 HibernateJpaVendorAdapterOpenJpaVendorAdapterEclipseLinkJpaVendorAdapterTopLinkJpaVendorAdapter 四個實現。其中最重要的屬性是 database,用來指定使用的數據庫類型,從而能根據數據庫類型來決定好比如何將數據庫特定異常轉換爲Spring的一致性異常,目前支持以下數據庫(DB二、DERBY、H二、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE)

public interface JpaVendorAdapter
{
  // 返回一個具體的持久層提供者
  public abstract PersistenceProvider getPersistenceProvider();

  // 返回持久層提供者的包名
  public abstract String getPersistenceProviderRootPackage();

  // 返回持久層提供者的屬性
  public abstract Map<String, ?> getJpaPropertyMap();

  // 返回JpaDialect
  public abstract JpaDialect getJpaDialect();

  // 返回持久層管理器工廠
  public abstract Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface();

  // 返回持久層管理器
  public abstract Class<? extends EntityManager> getEntityManagerInterface();

  // 自定義回調方法
  public abstract void postProcessEntityManagerFactory(EntityManagerFactory paramEntityManagerFactory);
}
複製代碼

咱們來看其中一個適配器實現類 HibernateJpaVendorAdapter

public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
    //設定持久層提供者
    private final PersistenceProvider persistenceProvider;
    //設定持久層方言
    private final JpaDialect jpaDialect;

    public HibernateJpaVendorAdapter() {
        this.persistenceProvider = new HibernatePersistence();
        this.jpaDialect = new HibernateJpaDialect();
    }

    //返回持久層方言
    public PersistenceProvider getPersistenceProvider() {
        return this.persistenceProvider;
    }

    //返回持久層提供者
    public String getPersistenceProviderRootPackage() {
        return "org.hibernate";
    }

    //返回JPA的屬性
    public Map<String, Object> getJpaPropertyMap() {
        Map jpaProperties = new HashMap();

        if (getDatabasePlatform() != null) {
            jpaProperties.put("hibernate.dialect", getDatabasePlatform());
        } else if (getDatabase() != null) {
            Class databaseDialectClass = determineDatabaseDialectClass(getDatabase());
            if (databaseDialectClass != null) {
                jpaProperties.put("hibernate.dialect",
                        databaseDialectClass.getName());
            }
        }

        if (isGenerateDdl()) {
            jpaProperties.put("hibernate.hbm2ddl.auto", "update");
        }
        if (isShowSql()) {
            jpaProperties.put("hibernate.show_sql", "true");
        }

        return jpaProperties;
    }

    //設定數據庫
    protected Class determineDatabaseDialectClass(Database database)     
    {                                                                                       
        switch (1.$SwitchMap$org$springframework$orm$jpa$vendor$Database[database.ordinal()]) 
        {                                                                                     
        case 1:                                                                             
          return DB2Dialect.class;                                                            
        case 2:                                                                               
          return DerbyDialect.class;                                                          
        case 3:                                                                               
          return H2Dialect.class;                                                             
        case 4:                                                                               
          return HSQLDialect.class;                                                           
        case 5:                                                                               
          return InformixDialect.class;                                                       
        case 6:                                                                               
          return MySQLDialect.class;                                                          
        case 7:                                                                               
          return Oracle9iDialect.class;                                                       
        case 8:                                                                               
          return PostgreSQLDialect.class;                                                     
        case 9:                                                                               
          return SQLServerDialect.class;                                                      
        case 10:                                                                              
          return SybaseDialect.class; }                                                       
        return null;              
    }

    //返回JPA方言
    public JpaDialect getJpaDialect() {
        return this.jpaDialect;
    }

    //返回JPA實體管理器工廠
    public Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface() {
        return HibernateEntityManagerFactory.class;
    }

    //返回JPA實體管理器
    public Class<? extends EntityManager> getEntityManagerInterface() {
        return HibernateEntityManager.class;
    }
}
複製代碼

配置文件中能夠這樣指定

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
   <property name="generateDdl" value="false" />  
   <property name="database" value="HSQL"/>  
</bean>  
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>  
複製代碼

spring MVC中的適配器模式

Spring MVC中的適配器模式主要用於執行目標 Controller 中的請求處理方法。

在Spring MVC中,DispatcherServlet 做爲用戶,HandlerAdapter 做爲指望接口,具體的適配器實現類用於對目標類進行適配,Controller 做爲須要適配的類。

爲何要在 Spring MVC 中使用適配器模式?Spring MVC 中的 Controller 種類衆多,不一樣類型的 Controller 經過不一樣的方法來對請求進行處理。若是不利用適配器模式的話,DispatcherServlet 直接獲取對應類型的 Controller,須要的自行來判斷,像下面這段代碼同樣:

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
}  
複製代碼

這樣假設若是咱們增長一個 HardController,就要在代碼中加入一行 if(mappedHandler.getHandler() instanceof HardController),這種形式就使得程序難以維護,也違反了設計模式中的開閉原則 – 對擴展開放,對修改關閉。

咱們來看看源碼,首先是適配器接口 HandlerAdapter

public interface HandlerAdapter {
    boolean supports(Object var1);

    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}
複製代碼

現該接口的適配器每個 Controller 都有一個適配器與之對應,這樣的話,每自定義一個 Controller 須要定義一個實現 HandlerAdapter 的適配器。

springmvc 中提供的 Controller 實現類有以下

spring mvc Controller 提供的實現類

springmvc 中提供的 HandlerAdapter 實現類以下

spring mvc HandlerAdapter 提供的實現類

HttpRequestHandlerAdapter 這個適配器代碼以下

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    public HttpRequestHandlerAdapter() {
    }

    public boolean supports(Object handler) {
        return handler instanceof HttpRequestHandler;
    }

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ((HttpRequestHandler)handler).handleRequest(request, response);
        return null;
    }

    public long getLastModified(HttpServletRequest request, Object handler) {
        return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;
    }
}
複製代碼

當Spring容器啓動後,會將全部定義好的適配器對象存放在一個List集合中,當一個請求來臨時,DispatcherServlet 會經過 handler 的類型找到對應適配器,並將該適配器對象返回給用戶,而後就能夠統一經過適配器的 hanle() 方法來調用 Controller 中的用於處理請求的方法。

public class DispatcherServlet extends FrameworkServlet {
    private List<HandlerAdapter> handlerAdapters;
    
    //初始化handlerAdapters
    private void initHandlerAdapters(ApplicationContext context) {
        //..省略...
    }
    
    // 遍歷全部的 HandlerAdapters,經過 supports 判斷找到匹配的適配器
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
	}
	
	// 分發請求,請求須要找到匹配的適配器來處理
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;

		// Determine handler for the current request.
		mappedHandler = getHandler(processedRequest);
			
		// 肯定當前請求的匹配的適配器.
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		ha.getLastModified(request, mappedHandler.getHandler());
					
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    }
	// ...省略...
}	
複製代碼

經過適配器模式咱們將全部的 controller 統一交給 HandlerAdapter 處理,免去了寫大量的 if-else 語句對 Controller 進行判斷,也更利於擴展新的 Controller 類型。

參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
孤落:Spring MVC中的適配器模式
ToughMind_:深刻淺出設計模式(五):7.適配器模式

相關文章
相關標籤/搜索