适配器模式原理及实例介绍
系列內(nèi)容:
此內(nèi)容是該系列?3?部分中的第?2?部分:?Java編程設(shè)計(jì)模式
適配器模式基本介紹
著名的設(shè)計(jì)模式“四人幫”這樣評(píng)價(jià)適配器模式:
將一個(gè)類(lèi)的接口轉(zhuǎn)換成客戶(hù)希望的另外一個(gè)接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些類(lèi)可以一起工作。——Gang of Four
適配器模式將一個(gè)類(lèi)的接口適配成用戶(hù)所期待的。一個(gè)適配器通常允許因?yàn)榻涌诓患嫒荻荒芤黄鸸ぷ鞯念?lèi)能夠在一起工作,做法是將類(lèi)自己的接口包裹在一個(gè)已存在的類(lèi)中。
Adapter 設(shè)計(jì)模式主要目的組合兩個(gè)不相干類(lèi),常用有兩種方法,第一種解決方案是修改各自類(lèi)的接口。但是如果沒(méi)有源碼,或者不愿意為了一個(gè)應(yīng)用而修改各自的接口,則需要使用 Adapter 適配器,在兩種接口之間創(chuàng)建一個(gè)混合接口。
圖 1. 適配器模式類(lèi)圖
圖 1 所示是適配器模式的類(lèi)圖。Adapter 適配器設(shè)計(jì)模式中有 3 個(gè)重要角色:被適配者 Adaptee,適配器 Adapter 和目標(biāo)對(duì)象 Target。其中兩個(gè)現(xiàn)存的想要組合到一起的類(lèi)分別是被適配者 Adaptee 和目標(biāo)對(duì)象 Target 角色,按照類(lèi)圖所示,我們需要?jiǎng)?chuàng)建一個(gè)適配器 Adapter 將其組合在一起。
具體實(shí)現(xiàn)代碼請(qǐng)見(jiàn)清單 1-4。
清單 1. 客戶(hù)端使用的接口
| 123456789 | /*?* 定義客戶(hù)端使用的接口,與業(yè)務(wù)相關(guān)?*/public interface Target {?/*?* 客戶(hù)端請(qǐng)求處理的方法?*/public void request();} |
清單 2. 被適配的對(duì)象
| 1234567891011 | /*?* 已經(jīng)存在的接口,這個(gè)接口需要配置?*/public class Adaptee {?/*?* 原本存在的方法?*/public void specificRequest(){//業(yè)務(wù)代碼}} |
清單 3. 適配器實(shí)現(xiàn)
| 12345678910111213141516171819202122 | /*?* 適配器類(lèi)?*/public class Adapter implements Target{?/*?* 持有需要被適配的接口對(duì)象?*/private Adaptee adaptee;/*?* 構(gòu)造方法,傳入需要被適配的對(duì)象?* @param adaptee 需要被適配的對(duì)象?*/public Adapter(Adaptee adaptee){this.adaptee = adaptee;}@Overridepublic void request() {// TODO Auto-generated method stubadaptee.specificRequest();}??} |
清單 4. 客戶(hù)端代碼
| 12345678910111213 | /*?* 使用適配器的客戶(hù)端?*/public class Client {?public static void main(String[] args){?//創(chuàng)建需要被適配的對(duì)象?Adaptee adaptee = new Adaptee();?//創(chuàng)建客戶(hù)端需要調(diào)用的接口對(duì)象?Target target = new Adapter(adaptee);?//請(qǐng)求處理?target.request();?}} |
以下情況比較適合使用 Adapter 模式:
當(dāng)你想使用一個(gè)已經(jīng)存在的類(lèi),而它的接口不符合你的需求;
你想創(chuàng)建一個(gè)可以復(fù)用的類(lèi),該類(lèi)可以與其他不相關(guān)的類(lèi)或不可預(yù)見(jiàn)的類(lèi)協(xié)同工作;
你想使用一些已經(jīng)存在的子類(lèi),但是不可能對(duì)每一個(gè)都進(jìn)行子類(lèi)化以匹配它們的接口,對(duì)象適配器可以適配它的父親接口。
適配器模式使用示例代碼
考慮一個(gè)記錄日志的應(yīng)用,用戶(hù)可能會(huì)提出要求采用文件的方式存儲(chǔ)日志,也可能會(huì)提出存儲(chǔ)日志到數(shù)據(jù)庫(kù)的需求,這樣我們可以采用適配器模式對(duì)舊的日志類(lèi)進(jìn)行改造,提供新的支持方式。
首先我們需要一個(gè)簡(jiǎn)單的日志對(duì)象類(lèi),如清單 5 所示。
清單 5. 日志對(duì)象類(lèi)
| 123456789101112131415161718192021222324 | /*?* 日志數(shù)據(jù)對(duì)象?*/public class LogBean {?private String logId;//日志編號(hào)?private String opeUserId;//操作人員???public String getLogId(){?return logId;?}?public void setLogId(String logId){?this.logId = logId;?}???public String getOpeUserId(){?return opeUserId;?}?public void setOpeUserId(String opeUserId){?this.opeUserId = opeUserId;}public String toString(){?return "logId="+logId+",opeUserId="+opeUserId;}} |
接下來(lái)定義一個(gè)操作日志文件的接口,代碼如清單 6 所示。
清單 6. 操作日志接口
| 1234567891011121314 | import java.util.List;/*?* 讀取日志文件,從文件里面獲取存儲(chǔ)的日志列表對(duì)象?* @return 存儲(chǔ)的日志列表對(duì)象?*/public interface LogFileOperateApi {?public List<LogBean> readLogFile();?/**?* 寫(xiě)日志文件,把日志列表寫(xiě)出到日志文件中去?* @param list 要寫(xiě)到日志文件的日志列表?*/?public void writeLogFile(List<LogBean> list);} |
然后實(shí)現(xiàn)日志文件的存儲(chǔ)和獲取,這里忽略業(yè)務(wù)代碼,代碼如清單 7 所示。
清單 7. 實(shí)現(xiàn)對(duì)日志文件的獲取
| 12345678910111213141516171819202122232425262728293031323334353637383940 | import java.io.File;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.List;/*?* 實(shí)現(xiàn)對(duì)日志文件的操作?*/public class LogFileOperate implements LogFileOperateApi{?/*?* 設(shè)置日志文件的路徑和文件名稱(chēng)?*/private String logFileName = "file.log";/*?* 構(gòu)造方法,傳入文件的路徑和名稱(chēng)?*/public LogFileOperate(String logFilename){if(logFilename!=null){this.logFileName = logFilename;}}@Overridepublic List<LogBean> readLogFile() {// TODO Auto-generated method stubList<LogBean> list = null;ObjectInputStream oin =null;//業(yè)務(wù)代碼return list;}@Overridepublic void writeLogFile(List<LogBean> list) {// TODO Auto-generated method stubFile file = new File(logFileName);ObjectOutputStream oout = null;//業(yè)務(wù)代碼}} |
如果這時(shí)候需要引入數(shù)據(jù)庫(kù)方式,引入適配器之前,我們需要定義日志管理的操作接口,代碼如清單 8 所示。
清單 8. 定義數(shù)據(jù)庫(kù)操作接口
| 1234567 | public interface LogDbOpeApi {?/*?* 新增日志?* @param 需要新增的日志對(duì)象?*/public void createLog(LogBean logbean);} |
接下來(lái)就要實(shí)現(xiàn)適配器了,LogDbOpeApi 接口就相當(dāng)于 Target 接口,LogFileOperate 就相當(dāng)于 Adaptee 類(lèi)。Adapter 類(lèi)代碼如清單 9 所示。
清單 9. Adapter 類(lèi)實(shí)現(xiàn)
| 123456789101112131415161718 | import java.util.List;/*?* 適配器對(duì)象,將記錄日志到文件的功能適配成數(shù)據(jù)庫(kù)功能?*/public class LogAdapter implements LogDbOpeApi{?private LogFileOperateApi adaptee;?public LogAdapter(LogFileOperateApi adaptee){?this.adaptee = adaptee;?}@Overridepublic void createLog(LogBean logbean) {// TODO Auto-generated method stubList<LogBean> list = adaptee.readLogFile();list.add(logbean);adaptee.writeLogFile(list);}} |
最后是客戶(hù)端代碼的實(shí)現(xiàn),如清單 10 所示。
清單 10. 客戶(hù)端類(lèi)實(shí)現(xiàn)
| 12345678910111213141516 | import java.util.ArrayList;import java.util.List;public class LogClient {?public static void main(String[] args){?LogBean logbean = new LogBean();?logbean.setLogId("1");?logbean.setOpeUserId("michael");?List<LogBean> list = new ArrayList<LogBean>();?LogFileOperateApi logFileApi = new LogFileOperate("");?//創(chuàng)建操作日志的接口對(duì)象?LogDbOpeApi api = new LogAdapter(logFileApi);?api.createLog(logbean);?}} |
適配器模式在開(kāi)源項(xiàng)目中的應(yīng)用
JDK 中有大量使用適配器模式的案例,清單 11 大致列舉了一些類(lèi)。
清單 11. 使用適配器模式的類(lèi)
| 123456 | java.util.Arrays#asList()javax.swing.JTable(TableModel)java.io.InputStreamReader(InputStream)java.io.OutputStreamWriter(OutputStream)javax.xml.bind.annotation.adapters.XmlAdapter#marshal()javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal() |
JDK1.1 之前提供的容器有 Arrays,Vector,Stack,Hashtable,Properties,BitSet,其中定義了一種訪問(wèn)群集內(nèi)各元素的標(biāo)準(zhǔn)方式,稱(chēng)為 Enumeration(列舉器)接口,用法如清單 12 所示。
清單 12.Enumeration 接口實(shí)現(xiàn)方式
| 12345 | Vector v=new Vector();for (Enumeration enum =v.elements(); enum.hasMoreElements();) {Object o = enum.nextElement();processObject(o);} |
JDK1.2 版本中引入了 Iterator 接口,新版本的集合對(duì)象(HashSet,HashMap,WeakHeahMap,ArrayList,TreeSet,TreeMap, LinkedList)是通過(guò) Iterator 接口訪問(wèn)集合元素的,用法如清單 13 所示。
清單 13. Iterator 接口實(shí)現(xiàn)方式
| 12345 | List list=new ArrayList();for(Iterator it=list.iterator();it.hasNext();){?System.out.println(it.next());} |
這樣,如果將老版本的程序運(yùn)行在新的 Java 編譯器上就會(huì)出錯(cuò)。因?yàn)?List 接口中已經(jīng)沒(méi)有 elements(),而只有 iterator() 了。那么如何將老版本的程序運(yùn)行在新的 Java 編譯器上呢? 如果不加修改,是肯定不行的,但是修改要遵循“開(kāi)-閉”原則。我們可以用 Java 設(shè)計(jì)模式中的適配器模式解決這個(gè)問(wèn)題。清單 14 所示是解決方法代碼。
清單 14. 采用適配器模式
| 1234567891011121314151617181920212223242526272829303132333435363738 | import java.util.ArrayList;import java.util.Enumeration;import java.util.Iterator;import java.util.List;public class NewEnumeration implements Enumeration{?Iterator it;?public NewEnumeration(Iterator it)?{?this.it=it;?// TODO Auto-generated constructor stub?}?public boolean hasMoreElements()?{?// TODO Auto-generated method stub?return it.hasNext();?}?public Object nextElement()?{?// TODO Auto-generated method stub?return it.next();?}?public static void main(String[] args)?{?List list=new ArrayList();?list.add("a");?list.add("b");?list.add("C");?for(Enumeration e=new NewEnumeration(list.iterator());e.hasMoreElements();)?{?System.out.println(e.nextElement());?}?}} |
清單 14 所示的 NewEnumeration 是一個(gè)適配器類(lèi),通過(guò)它實(shí)現(xiàn)了從 Iterator 接口到 Enumeration 接口的適配,這樣我們就可以使用老版本的代碼來(lái)使用新的集合對(duì)象了。
Java I/O 庫(kù)大量使用了適配器模式,例如 ByteArrayInputStream 是一個(gè)適配器類(lèi),它繼承了 InputStream 的接口,并且封裝了一個(gè) byte 數(shù)組。換言之,它將一個(gè) byte 數(shù)組的接口適配成 InputStream 流處理器的接口。
我們知道 Java 語(yǔ)言支持四種類(lèi)型:Java 接口,Java 類(lèi),Java 數(shù)組,原始類(lèi)型(即 int,float 等)。前三種是引用類(lèi)型,類(lèi)和數(shù)組的實(shí)例是對(duì)象,原始類(lèi)型的值不是對(duì)象。也即,Java 語(yǔ)言的數(shù)組是像所有的其他對(duì)象一樣的對(duì)象,而不管數(shù)組中所存儲(chǔ)的元素類(lèi)型是什么。這樣一來(lái)的話(huà),ByteArrayInputStream 就符合適配器模式的描述,是一個(gè)對(duì)象形式的適配器類(lèi)。FileInputStream 是一個(gè)適配器類(lèi)。在 FileInputStream 繼承了 InputStrem 類(lèi)型,同時(shí)持有一個(gè)對(duì) FileDiscriptor 的引用。這是將一個(gè) FileDiscriptor 對(duì)象適配成 InputStrem 類(lèi)型的對(duì)象形式的適配器模式。查看 JDK1.4 的源代碼我們可以看到清單 15 所示的 FileInputStream 類(lèi)的源代碼。
清單 15. FileInputStream 類(lèi)
| 1234567891011121314151617181920212223 | Public class FileInputStream extends InputStream{/* File Descriptor - handle to the open file */private FileDescriptor fd;public FileInputStream(FileDescriptor fdObj) {SecurityManager security = System.getSecurityManager(); if (fdObj == null) {throw new NullPointerException();}if (security != null) {security.checkRead(fdObj);}fd = fdObj; } public FileInputStream(File file) throws FileNotFoundException {String name = file.getPath();SecurityManager security = System.getSecurityManager();if (security != null) {security.checkRead(name);}fd = new FileDescriptor();open(name);}//其它代碼}} |
同樣地,在 OutputStream 類(lèi)型中,所有的原始流處理器都是適配器類(lèi)。ByteArrayOutputStream 繼承了 OutputStream 類(lèi)型,同時(shí)持有一個(gè)對(duì) byte 數(shù)組的引用。它一個(gè) byte 數(shù)組的接口適配成 OutputString 類(lèi)型的接口,因此也是一個(gè)對(duì)象形式的適配器模式的應(yīng)用。
FileOutputStream 繼承了 OutputStream 類(lèi)型,同時(shí)持有一個(gè)對(duì) FileDiscriptor 對(duì)象的引用。這是一個(gè)將 FileDiscriptor 接口適配成 OutputStream 接口形式的對(duì)象型適配器模式。
Reader 類(lèi)型的原始流處理器都是適配器模式的應(yīng)用。StringReader 是一個(gè)適配器類(lèi),StringReader 類(lèi)繼承了 Reader 類(lèi)型,持有一個(gè)對(duì) String 對(duì)象的引用。它將 String 的接口適配成 Reader 類(lèi)型的接口。
Spring 中使用適配器模式的典型應(yīng)用
在 Spring 的 AOP 里通過(guò)使用的 Advice(通知)來(lái)增強(qiáng)被代理類(lèi)的功能。Spring 實(shí)現(xiàn)這一 AOP 功能的原理就使用代理模式(1、JDK 動(dòng)態(tài)代理。2、CGLib 字節(jié)碼生成技術(shù)代理。)對(duì)類(lèi)進(jìn)行方法級(jí)別的切面增強(qiáng),即,生成被代理類(lèi)的代理類(lèi),并在代理類(lèi)的方法前,設(shè)置攔截器,通過(guò)執(zhí)行攔截器中的內(nèi)容增強(qiáng)了代理方法的功能,實(shí)現(xiàn)的面向切面編程。
Advice(通知)的類(lèi)型有:BeforeAdvice、AfterReturningAdvice、ThrowSadvice 等。每個(gè)類(lèi)型 Advice(通知)都有對(duì)應(yīng)的攔截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring 需要將每個(gè) Advice(通知)都封裝成對(duì)應(yīng)的攔截器類(lèi)型,返回給容器,所以需要使用適配器模式對(duì) Advice 進(jìn)行轉(zhuǎn)換。具體代碼如清單 16-19 所示。
清單 16. MethodBeforeAdvice 類(lèi)
| 12345678910 | public interface MethodBeforeAdvice extends BeforeAdvice { ???void before(Method method, Object[] args, Object target) throws Throwable; ??} public interface MethodBeforeAdvice extends BeforeAdvice {void before(Method method, Object[] args, Object target) throws Throwable;} |
清單 17. Adapter 類(lèi)接口
| 1234567891011121314 | public interface AdvisorAdapter { ???boolean supportsAdvice(Advice advice); ???MethodInterceptor getInterceptor(Advisor advisor); ??} public interface AdvisorAdapter {boolean supportsAdvice(Advice advice);MethodInterceptor getInterceptor(Advisor advisor);} |
清單 18. MethodBeforeAdviceAdapter 類(lèi)
| 123456789101112131415161718192021222324 | class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { ???public boolean supportsAdvice(Advice advice) { ?return (advice instanceof MethodBeforeAdvice); ?} ???public MethodInterceptor getInterceptor(Advisor advisor) { ?MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); ?return new MethodBeforeAdviceInterceptor(advice); ?} ??} class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {public boolean supportsAdvice(Advice advice) {return (advice instanceof MethodBeforeAdvice);}public MethodInterceptor getInterceptor(Advisor advisor) {MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();return new MethodBeforeAdviceInterceptor(advice);}} |
清單 19. DefaultAdvisorAdapterRegistry 類(lèi)
| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 | public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { ???private final List<AdvisorAdapter> adapters = new ArrayList<AdvisorAdapter>(3); ?????/** ?* Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters. ?*/ ?public DefaultAdvisorAdapterRegistry() {//這里注冊(cè)了適配器 ?registerAdvisorAdapter(new MethodBeforeAdviceAdapter()); ?registerAdvisorAdapter(new AfterReturningAdviceAdapter()); ?registerAdvisorAdapter(new ThrowsAdviceAdapter()); ?} ?????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) { ?// Check that it is supported. ?if (adapter.supportsAdvice(advice)) {//這里調(diào)用了適配器的方法 ?return new DefaultPointcutAdvisor(advice); ?} ?} ?throw new UnknownAdviceTypeException(advice); ?} ???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)) {//這里調(diào)用了適配器的方法 ?interceptors.add(adapter.getInterceptor(advisor)); ?} ?} ?if (interceptors.isEmpty()) { ?throw new UnknownAdviceTypeException(advisor.getAdvice()); ?} ?return interceptors.toArray(new MethodInterceptor[interceptors.size()]); ?} ???public void registerAdvisorAdapter(AdvisorAdapter adapter) { ?this.adapters.add(adapter); ?} ??} |
雙向適配器
適配器也可以實(shí)現(xiàn)雙向的適配,前面所講的都是把 Adaptee 適配成為 Target,其實(shí)也可以把 Target 適配成為 Adaptee。也就是說(shuō)這個(gè)適配器可以同時(shí)當(dāng)作 Target 和 Adaptee 來(lái)使用。
清單 20. TwiceAdapter 類(lèi)
| 1234567891011121314151617181920212223242526272829303132333435363738394041 | import java.util.List;/*?* 雙向適配器對(duì)象案例?*/public class TwiceAdapter implements LogDbOpeApi,LogFileOperateApi {??/*?* 持有需要被適配的文件存儲(chǔ)日志的接口對(duì)象?*/private LogFileOperateApi fileLog;/*?* 持有需要被適配的 DB 存儲(chǔ)日志的接口對(duì)象?*/private LogDbOpeApi dbLog;public TwiceAdapter(LogFileOperateApi fileLog,LogDbOpeApi dbLog){this.fileLog = fileLog;this.dbLog = dbLog;}@Overridepublic List<LogBean> readLogFile() {// TODO Auto-generated method stubreturn null;}@Overridepublic void writeLogFile(List<LogBean> list) {// TODO Auto-generated method stub}@Overridepublic void createLog(LogBean logbean) {// TODO Auto-generated method stubList<LogBean> list = fileLog.readLogFile();list.add(logbean);fileLog.writeLogFile(list);}} |
雙向適配器同時(shí)實(shí)現(xiàn)了 Target 和 Adaptee 的接口,使得雙向適配器可以在 Target 或 Adaptee 被使用的地方使用,以提供對(duì)所有客戶(hù)的透明性。尤其在兩個(gè)不同的客戶(hù)需要用不同的地方查看同一個(gè)對(duì)象時(shí),適合使用雙向適配器。
對(duì)象適配器和類(lèi)適配器
在標(biāo)準(zhǔn)的適配器模式里面,根據(jù)適配器的實(shí)現(xiàn)方式,把適配器分成對(duì)象適配器和類(lèi)適配器。
對(duì)象適配器
依賴(lài)于對(duì)象的組合,都是采用對(duì)象組合的方式,也就是對(duì)象適配器實(shí)現(xiàn)的方式。
類(lèi)適配器
采用多重繼承對(duì)一個(gè)接口與另一個(gè)接口進(jìn)行匹配。由于 Java 不支持多重繼承,所以到目前為止還沒(méi)有涉及。但可以通過(guò)讓適配器去實(shí)現(xiàn) Target 接口的方式來(lái)實(shí)現(xiàn)。
清單 21. ClassAdapter 類(lèi)
| 123456789101112131415161718192021 | import java.util.List;/*?* 類(lèi)適配器對(duì)象案例?*/public class ClassAdapter extends LogFileOperate implements LogDbOpeApi{public ClassAdapter(String logFilename) {super(logFilename);// TODO Auto-generated constructor stub}@Overridepublic void createLog(LogBean logbean) {// TODO Auto-generated method stubList<LogBean> list = this.readLogFile();list.add(logbean);this.writeLogFile(list);}??} |
在實(shí)現(xiàn)中,主要是適配器的實(shí)現(xiàn)與以前不一樣,與對(duì)象適配器實(shí)現(xiàn)同樣的功能相比,類(lèi)適配器在實(shí)現(xiàn)上有所改變:
需要繼承 LogFileOperate 的實(shí)現(xiàn),然后再實(shí)現(xiàn) LogDbOpeApi 接口;
需要按照繼承 LogFileOperate 的要求,提供傳入文件路徑和名稱(chēng)的構(gòu)造方法;
不再需要持有 LogFileOperate 的對(duì)象,因?yàn)檫m配器本身就是 LogFileOperate 對(duì)象的子類(lèi);
以前調(diào)用被適配對(duì)象的方法的地方,全部修改成調(diào)用自己的方法。
類(lèi)適配器和對(duì)象適配器的選擇
從實(shí)現(xiàn)上:類(lèi)適配器使用對(duì)象繼承的方式,屬于靜態(tài)的定義方式。對(duì)象適配器使用對(duì)象組合的方式,屬于動(dòng)態(tài)組合的方式;
從工作模式上:類(lèi)適配器直接繼承了 Adaptee,使得適配器不能和 Adaptee 的子類(lèi)一起工作。對(duì)象適配器允許一個(gè) Adapter 和多個(gè) Adaptee,包括 Adaptee 和它所有的子類(lèi)一起工作;
從定義角度:類(lèi)適配器可以重定義 Adaptee 的部分行為,相當(dāng)于子類(lèi)覆蓋父類(lèi)的部分實(shí)現(xiàn)方法。對(duì)象適配器要重定義 Adaptee 很困難;
從開(kāi)發(fā)角度:類(lèi)適配器僅僅引入了一個(gè)對(duì)象,并不需要額外的引用來(lái)間接得到 Adaptee。對(duì)象適配器需要額外的引用來(lái)間接得到 Adaptee。
總的來(lái)說(shuō),建議使用對(duì)象適配器方式。
適配器模式使用注意事項(xiàng)
充當(dāng)適配器角色的類(lèi)就是:實(shí)現(xiàn)已有接口的抽象類(lèi);
為什么要用抽象類(lèi)?此類(lèi)是不要被實(shí)例化的。而只充當(dāng)適配器的角色,也就為其子類(lèi)提供了一個(gè)共同的接口,但其子類(lèi)又可以將精力只集中在其感興趣的地方。
適配器模式中被適配的接口 Adaptee 和適配成為的接口 Target 是沒(méi)有關(guān)聯(lián)的,Adaptee 和 Target 中的方法既可以是相同的,也可以是不同的。
適配器在適配的時(shí)候,可以適配多個(gè) Apaptee,也就是說(shuō)實(shí)現(xiàn)某個(gè)新的 Target 的功能的時(shí)候,需要調(diào)用多個(gè)模塊的功能,適配多個(gè)模塊的功能才能滿(mǎn)足新接口的要求。
適配器有一個(gè)潛在的問(wèn)題,就是被適配的對(duì)象不再兼容 Adaptee 的接口,因?yàn)檫m配器只是實(shí)現(xiàn)了 Target 的接口。這導(dǎo)致并不是所有 Adaptee 對(duì)象可以被使用的地方都能是使用適配器,雙向適配器解決了這個(gè)問(wèn)題。
優(yōu)點(diǎn)
適配器模式也是一種包裝模式,它與裝飾模式同樣具有包裝的功能,此外,對(duì)象適配器模式還具有委托的意思。總的來(lái)說(shuō),適配器模式屬于補(bǔ)償模式,專(zhuān)用來(lái)在系統(tǒng)后期擴(kuò)展、修改時(shí)使用。
缺點(diǎn)
過(guò)多的使用適配器,會(huì)讓系統(tǒng)非常零亂,不易整體進(jìn)行把握。比如,明明看到調(diào)用的是 A 接口,其實(shí)內(nèi)部被適配成了 B 接口的實(shí)現(xiàn),一個(gè)系統(tǒng)如果太多出現(xiàn)這種情況,無(wú)異于一場(chǎng)災(zāi)難。因此如果不是很有必要,可以不使用適配器,而是直接對(duì)系統(tǒng)進(jìn)行重構(gòu)。
適配器模式應(yīng)用場(chǎng)景
在軟件開(kāi)發(fā)中,也就是系統(tǒng)的數(shù)據(jù)和行為都正確,但接口不相符時(shí),我們應(yīng)該考慮用適配器,目的是使控制范圍之外的一個(gè)原有對(duì)象與某個(gè)接口匹配。適配器模式主要應(yīng)用于希望復(fù)用一些現(xiàn)存的類(lèi),但是接口又與復(fù)用環(huán)境要求不一致的情況。比如在需要對(duì)早期代碼復(fù)用一些功能等應(yīng)用上很有實(shí)際價(jià)值。適用場(chǎng)景大致包含三類(lèi):
1、已經(jīng)存在的類(lèi)的接口不符合我們的需求;
2、創(chuàng)建一個(gè)可以復(fù)用的類(lèi),使得該類(lèi)可以與其他不相關(guān)的類(lèi)或不可預(yù)見(jiàn)的類(lèi)(即那些接口可能不一定兼容的類(lèi))協(xié)同工作;
3、在不對(duì)每一個(gè)都進(jìn)行子類(lèi)化以匹配它們的接口的情況下,使用一些已經(jīng)存在的子類(lèi)。
結(jié)束語(yǔ)
本文對(duì)適配器模式做了一些介紹,希望能夠幫助讀者對(duì)適配器模式有進(jìn)一步的了解。適配器模式也是一種包裝模式,它與裝飾模式同樣具有包裝的功能,此外,對(duì)象適配器模式還具有委托的意思。總的來(lái)說(shuō),適配器模式屬于補(bǔ)償模式,專(zhuān)門(mén)用來(lái)在系統(tǒng)后期擴(kuò)展、修改時(shí)使用,但要注意不要過(guò)度使用適配器模式。
相關(guān)主題
- 參考 IBM?developerWorks 中國(guó)關(guān)于適配器模式知識(shí)檢索頁(yè)面,查看 IBM 開(kāi)發(fā)者論壇公布的關(guān)于適配器模式的相關(guān)文章。
- 查看書(shū)籍《設(shè)計(jì)模式》,作者 Erich Gamm,Richard Helm,Ralph Johnson,John Vlissides,最早、最經(jīng)典的設(shè)計(jì)模式書(shū)籍。
- developerWorks Java 技術(shù)專(zhuān)區(qū):這里有數(shù)百篇關(guān)于 Java 編程各個(gè)方面的文章。
from:?https://www.ibm.com/developerworks/cn/java/j-lo-adapter-pattern/index.html
總結(jié)
以上是生活随笔為你收集整理的适配器模式原理及实例介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java设计模式 - 适配器模式
- 下一篇: (十)装饰器模式详解(与IO不解的情缘)