設計模式 | 介面卡模式及典型應用
ofollow,noindex">設計模式 | 簡單工廠模式及典型應用
設計模式 | 裝飾者模式及典型應用更多內容可訪問我的個人部落格:laijianfeng.org
關注【小旋鋒】微信公眾號,及時接收博文推送

介面卡模式
介面卡模式(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交流電類
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 newIllegalArgumentException("沒有找到合適的變壓介面卡"); } 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 複製程式碼
介面卡模式總結
主要優點:
- 將目標類和適配者類解耦,通過引入一個介面卡類來重用現有的適配者類,無須修改原有結構。
- 增加了類的透明性和複用性,將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,而且提高了適配者的複用性,同一個適配者類可以在多個不同的系統中複用。
- 靈活性和擴充套件性都非常好,通過使用配置檔案,可以很方便地更換介面卡,也可以在不修改原有程式碼的基礎上增加新的介面卡類,完全符合“開閉原則”。
具體來說,類介面卡模式還有如下優點:
- 由於介面卡類是適配者類的子類,因此可以在介面卡類中置換一些適配者的方法,使得介面卡的靈活性更強。
物件介面卡模式還有如下優點:
- 一個物件介面卡可以把多個不同的適配者適配到同一個目標;
- 可以適配一個適配者的子類,由於介面卡和適配者之間是關聯關係,根據“里氏代換原則”,適配者的子類也可通過該介面卡進行適配。
類介面卡模式的缺點如下:
- 對於Java、C#等不支援多重類繼承的語言,一次最多隻能適配一個適配者類,不能同時適配多個適配者;
- 適配者類不能為最終類,如在Java中不能為final類,C#中不能為sealed類;
- 在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 { @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()
建立對應型別的攔截器

這裡應該屬於物件介面卡模式,關鍵字 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<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
實現類有如下

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<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 方式+記憶體分析
ToughMind_: 深入淺出設計模式(五):7.介面卡模式