天天看點

簡述事件接口與事件擴充卡的聯系與差別_設計模式——擴充卡模式擴充卡模式示例擴充卡模式總結源碼分析擴充卡模式的典型應用

擴充卡模式

擴充卡模式(Adapter Pattern):将一個接口轉換成客戶希望的另一個接口,使接口不相容的那些類可以一起工作,其别名為包裝器(Wrapper)。擴充卡模式既可以作為類結構型模式,也可以作為對象結構型模式。

在擴充卡模式中,我們通過增加一個新的擴充卡類來解決接口不相容的問題,使得原本沒有任何關系的類可以協同工作。

根據擴充卡類與适配者類的關系不同,擴充卡模式可分為對象擴充卡和類擴充卡兩種,在對象擴充卡模式中,擴充卡與适配者之間是關聯關系;在類擴充卡模式中,擴充卡與适配者之間是繼承(或實作)關系。

角色

Target(目标抽象類):目标抽象類定義客戶所需接口,可以是一個抽象類或接口,也可以是具體類。

Adapter(擴充卡類):擴充卡可以調用另一個接口,作為一個轉換器,對Adaptee和Target進行适配,擴充卡類是擴充卡模式的核心,在對象擴充卡中,它通過繼承Target并關聯一個Adaptee對象使二者産生聯系。

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() 調用 Adaptee 的 adapteeRequest() 方法呢?

如果直接實作 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交流電類

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 adapters = new LinkedList();    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的類型有:MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice

在每個類型 Advice 都有對應的攔截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor

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 {@Overridepublic boolean supportsAdvice(Advice advice) {return (advice instanceof MethodBeforeAdvice);}@Overridepublic MethodInterceptor getInterceptor(Advisor advisor) {MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();return new MethodBeforeAdviceInterceptor(advice);}}@SuppressWarnings("serial")class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {@Overridepublic boolean supportsAdvice(Advice advice) {return (advice instanceof AfterReturningAdvice);}@Overridepublic MethodInterceptor getInterceptor(Advisor advisor) {AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();return new AfterReturningAdviceInterceptor(advice);}}class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {@Overridepublic boolean supportsAdvice(Advice advice) {return (advice instanceof ThrowsAdvice);}@Overridepublic MethodInterceptor getInterceptor(Advisor advisor) {return new ThrowsAdviceInterceptor(advisor.getAdvice());}}
           

用戶端 DefaultAdvisorAdapterRegistry

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {    private final List 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 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() 建立對應類型的攔截器

簡述事件接口與事件擴充卡的聯系與差別_設計模式——擴充卡模式擴充卡模式示例擴充卡模式總結源碼分析擴充卡模式的典型應用

這裡應該屬于對象擴充卡模式,關鍵字 instanceof 可看成是 Advice 的方法,不過這裡的 Advice 對象是從外部傳進來,而不是成員屬性

spring JPA中的擴充卡模式

在Spring的ORM包中,對于JPA的支援也是采用了擴充卡模式,首先定義了一個接口的 JpaVendorAdapter,然後不同的持久層架構都實作此接口。

jpaVendorAdapter:用于設定實作廠商JPA實作的特定屬性,如設定Hibernate的是否自動生成DDL的屬性generateDdl;這些屬性是廠商特定的,是以最好在這裡設定;目前Spring提供 HibernateJpaVendorAdapter、OpenJpaVendorAdapter、EclipseLinkJpaVendorAdapter、TopLinkJpaVendorAdapter 四個實作。其中最重要的屬性是 database,用來指定使用的資料庫類型,進而能根據資料庫類型來決定比如如何将資料庫特定異常轉換為Spring的一緻性異常,目前支援如下資料庫(DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE)

public interface JpaVendorAdapter{  // 傳回一個具體的持久層提供者  public abstract PersistenceProvider getPersistenceProvider();  // 傳回持久層提供者的包名  public abstract String getPersistenceProviderRootPackage();  // 傳回持久層提供者的屬性  public abstract Map 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 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;    }}
           

配置檔案中可以這樣指定

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 實作類有如下

簡述事件接口與事件擴充卡的聯系與差別_設計模式——擴充卡模式擴充卡模式示例擴充卡模式總結源碼分析擴充卡模式的典型應用

springmvc 中提供的 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 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 類型。