前言
本文介紹介面卡模式,原始碼分析spring aop, jpa, mvc中的介面卡模式
推薦閱讀
設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
設計模式 | 裝飾者模式及典型應用
更多內容可訪問我的個人部落格: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 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
複製程式碼
介面卡模式總結
主要優點:
- 將目標類和適配者類解耦,通過引入一個介面卡類來重用現有的適配者類,無須修改原有結構。
- 增加了類的透明性和複用性,將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,而且提高了適配者的複用性,同一個適配者類可以在多個不同的系統中複用。
- 靈活性和擴充套件性都非常好,通過使用配置檔案,可以很方便地更換介面卡,也可以在不修改原有程式碼的基礎上增加新的介面卡類,完全符合“開閉原則”。
具體來說,類介面卡模式還有如下優點:
- 由於介面卡類是適配者類的子類,因此可以在介面卡類中置換一些適配者的方法,使得介面卡的靈活性更強。
物件介面卡模式還有如下優點:
- 一個物件介面卡可以把多個不同的適配者適配到同一個目標;
- 可以適配一個適配者的子類,由於介面卡和適配者之間是關聯關係,根據“里氏代換原則”,適配者的子類也可通過該介面卡進行適配。
類介面卡模式的缺點如下:
- 對於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 方式+記憶體分析
孤落:Spring MVC中的介面卡模式
ToughMind_:深入淺出設計模式(五):7.介面卡模式