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

適配器模式

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

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

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

角色

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

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

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

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

示例

類適配器

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

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

定義一個目標接口app

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 AdvisorAdapterSerializable {
    @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 AdvisorAdapterSerializable {
    @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 AdvisorAdapterSerializable {
    @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 AdvisorAdapterRegistrySerializable {
    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.適配器模式  

推薦閱讀

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

點擊[閱讀原文]可訪問個人我的博客:http://laijianfeng.org

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


本文分享自微信公衆號 - 小旋鋒(whirlysBigData)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索