適配器模式(Adapter Pattern)
屬於結構型模式
的一種,把一個類的接口變成客戶端所期待的另外一種接口,從而使本來接口不匹配而沒法一塊兒工做的兩個類可以在一塊兒工做...html
<!-- more -->java
當你想使用一個已經存在的類,而它的接口不符合你的需求,或者你想建立一個可重用的類(與不兼容接口無關的類),這時候能夠考慮使用適配器模式
。同時它也是一種包裝模式,它與裝飾模式
一樣具備包裝的功能。git
筆者算是小米的忠實用戶了,從大學期間起至今都是購買的小米,期間發現小米5
在推出的時候,它會送一個type-c
的轉接口給咱們,那會type-c
數據線應該還不算普及,這種作法仍是蠻好的,在使用轉接口後Micro USB
得以重複利用,這樣一來即便原裝的米5數據線丟了也不要緊,只要有type-c
轉接口,同樣能夠用Micro USB
充電/鏈接電腦編程
1.首先定義M4DataLine
表明是Micro USB
,咱們目的就是經過適配器可以用米4數據線鏈接米5手機設計模式
class M4DataLine { public void connection() { System.out.println("使用小米4數據線鏈接..."); } }
2.定義客戶端使用的接口,與業務相關數組
interface Target { void connection(); } class M5DataLine implements Target { @Override public void connection() { System.out.println("使用小米5數據線鏈接..."); } }
3.建立適配器類,繼承了被適配類,同時實現標準接口微信
class M5DataLineAdapter extends M4DataLine implements Target { @Override public void connection() { System.out.println("插入 type-c 轉接頭"); super.connection(); } }
4.客戶端代碼,測試ide
public class AdapterMain { public static void main(String[] args) { Target target = new M5DataLine(); target.connection(); Target adapter = new M5DataLineAdapter(); adapter.connection(); } }
5.結果測試
使用小米5數據線鏈接... 插入 type-c 轉接頭 使用小米4數據線鏈接...
建立適配器類,實現標準接口,將這個調用委託給實現新接口的對象來處理this
class M5DataLineAdapter implements Target { private Target target; public M5DataLineAdapter(Target target) { this.target = target; } @Override public void connection() { System.out.println("插入 type-c 轉接頭"); target.connection(); } } public class AdapterMain { public static void main(String[] args) { // 使用特殊功能類,即適配類 Target adapter = new M5DataLineAdapter(new M5DataLine()); adapter.connection(); } }
類適配器:對象繼承的方式,靜態的定義。
對象適配器:依賴於對象的組合,都是採用對象組合的方式,也就是對象適配器實現的方式。
使用適配器模式的類
java.util.Arrays#asList() java.io.InputStreamReader(InputStream) java.io.OutputStreamWriter(OutputStream)
Java I/O 庫大量使用了適配器模式,如 ByteArrayInputStream
是一個適配器類,它繼承了 InputStream
的接口,而且封裝了一個 byte 數組。換言之,它將一個 byte 數組的接口適配成 InputStream 流處理器的接口。
在 OutputStream
類型中,全部的原始流處理器都是適配器類。ByteArrayOutputStream
繼承了 OutputStream
類型,同時持有一個對 byte 數組的引用。它一個 byte 數組的接口適配成 OutputString 類型的接口,所以也是一個對象形式的適配器模式的應用。
FileOutputStream
繼承了 OutputStream
類型,同時持有一個對 FileDiscriptor
對象的引用。這是一個將 FileDiscriptor
接口適配成 OutputStream
接口形式的對象型適配器模式。
Reader
類型的原始流處理器都是適配器模式的應用。StringReader
是一個適配器類,StringReader
類繼承了 Reader
類型,持有一個對 String 對象的引用。它將 String 的接口適配成 Reader
類型的接口。
在 Spring 的 AOP 裏經過使用的 Advice(通知)來加強被代理類的功能。Spring 實現這一 AOP 功能的原理就使用代理模式(一、JDK 動態代理。二、CGLib 字節碼生成技術代理。)對類進行方法級別的切面加強,即,生成被代理類的代理類,並在代理類的方法前,設置攔截器,經過執行攔截器中的內容加強了代理方法的功能,實現的面向切面編程。
Advice(通知)的類型有:BeforeAdvice、AfterReturningAdvice、ThrowSadvice 等。每一個類型 Advice(通知)都有對應的攔截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring 須要將每一個 Advice(通知)都封裝成對應的攔截器類型,返回給容器,因此須要使用適配器模式對 Advice 進行轉換。
public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method method, Object[] args, Object target) throws Throwable; } public interface AdvisorAdapter { boolean supportsAdvice(Advice advice); MethodInterceptor getInterceptor(Advisor advisor); } 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); } }
默認的適配器註冊表
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { private final List<AdvisorAdapter> adapters = new ArrayList<AdvisorAdapter>(3); public DefaultAdvisorAdapterRegistry() { // 註冊適配器 registerAdvisorAdapter(new MethodBeforeAdviceAdapter()); registerAdvisorAdapter(new AfterReturningAdviceAdapter()); registerAdvisorAdapter(new ThrowsAdviceAdapter()); } @Override public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { if (adviceObject instanceof Advisor) { return (Advisor) adviceObject; } if (!(adviceObject instanceof Advice)) { throw new UnknownAdviceTypeException(adviceObject); } Advice advice = (Advice) adviceObject; if (advice instanceof MethodInterceptor) { // So well-known it doesn't even need an adapter. return new DefaultPointcutAdvisor(advice); } for (AdvisorAdapter adapter : this.adapters) { // 檢查是否支持,這裏調用了適配器的方法 if (adapter.supportsAdvice(advice)) { return new DefaultPointcutAdvisor(advice); } } throw new UnknownAdviceTypeException(advice); } @Override public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3); Advice advice = advisor.getAdvice(); if (advice instanceof MethodInterceptor) { interceptors.add((MethodInterceptor) advice); } for (AdvisorAdapter adapter : this.adapters) { // 檢查是否支持,這裏調用了適配器的方法 if (adapter.supportsAdvice(advice)) { interceptors.add(adapter.getInterceptor(advisor)); } } if (interceptors.isEmpty()) { throw new UnknownAdviceTypeException(advisor.getAdvice()); } return interceptors.toArray(new MethodInterceptor[interceptors.size()]); } @Override public void registerAdvisorAdapter(AdvisorAdapter adapter) { this.adapters.add(adapter); } }
優勢
缺點
適用場景
小故事
魏文王問名醫扁鵲說:「大家家兄弟三人,都精於醫術,到底哪一位最好呢?」
扁鵲答:「大哥最好,二哥次之,我最差。」
文王再問:「那麼爲何你最出名呢?」
扁鵲答說:「我大哥治病,是治病於病情發做以前。因爲通常人不知道他率先能剷除病因,因此他的名氣沒法傳出去,只有咱們家的人才知道。我二哥治病,是治病於病情初起之時。通常人覺得他只能治輕微的小病,因此他的名氣只及於本鄉里。而我扁鵲治病,是治病於病情嚴重之時。通常人都看到我在經脈上穿針管來放血、在皮膚上敷藥等大手術,因此覺得個人醫術高明,名氣所以響遍全國。」
比較起來,能防範於未然是最高明的,但每每因防範在前,不會出現惡果,使事物保持了原態,沒有「明顯」的功績而被忽略。正如不見防火英雄,只有救火英雄同樣。高明者不見得必定名聲顯赫。
建議儘可能使用對象的適配器模式,少用繼承。適配器模式也是一種包裝模式,它與裝飾模式一樣具備包裝的功能,此外,對象適配器模式
還具備委託的意思。總的來講,適配器模式屬於補償模式,專門用來在系統後期擴展、修改時使用,但要注意不要過分使用適配器模式。
參考文獻:《大話設計模式》
IBM developerWorks:適配器模式原理及實例介紹
全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter5/battcn-adapter
微信公衆號:battcn
(歡迎調戲)