设计模式之适配器模式(Adapter Pattern)
在正式開始之前,讓我們先思考幾個(gè)問題:
- 如果現(xiàn)有的新項(xiàng)目可以利用舊項(xiàng)目里大量的遺留代碼,你打算從頭開始完成新項(xiàng)目還是去了解舊項(xiàng)目的模塊功能以及接口?
- 如果你了解過遺留代碼之后,發(fā)現(xiàn)有幾個(gè)重要的功能模塊接口不同(因?yàn)樗鼈兛赡軄碜远鄠€(gè)舊項(xiàng)目),無法直接復(fù)用,你打算放棄使用遺留代碼嗎?
- 如果你不打算放棄(這樣做應(yīng)該是對(duì)的,畢竟遺留代碼的正確性是經(jīng)過實(shí)踐檢驗(yàn)的),那么是不是只能去改寫剩余的n - 1個(gè)接口,甚至改寫所有的n個(gè)接口?
- 如果不這樣做,還有什么簡(jiǎn)單的方法嗎?
一.什么是適配器模式?
首先,我們需要知道適配器是什么東西,嗯,筆記本電腦的電源適配器聽說過吧?
它能夠把220V的交流電轉(zhuǎn)換為筆記本需要的15V直流電
太神奇了,一個(gè)小小的電源適配器解決了家庭用電與筆記本需要的電類型不匹配的問題
發(fā)現(xiàn)什么了么?
沒錯(cuò),我們既沒有改變家庭用電(把它變成15V直流電),也沒有改變筆記本(把它變成220V交流電),但我們確實(shí)解決了這個(gè)問題
-------
適配器模式——用來實(shí)現(xiàn)不同接口轉(zhuǎn)換的設(shè)計(jì)模式
二.舉個(gè)例子
假設(shè)我們有兩個(gè)封裝好的功能模塊,但它們需要的參數(shù)不同(雖然參數(shù)的實(shí)質(zhì)是同一種對(duì)象)
比如,我們的A模塊(文本檢查模塊)是這樣的:
package AdapterPattern;/*** @author ayqy* 文本檢查模塊(類似與MSOffice Word中的“拼寫和語法檢查”)*/ public class TextCheckModule {FormatText text;public TextCheckModule(FormatText text){this.text = text;}/** 省略很多具體Check操作。。*/ }A模塊入口需要一個(gè)FormatText類型的參數(shù),它的定義如下:
package AdapterPattern;/*** @author ayqy* 定義格式化文本*/ public interface FormatText {String text = null;/*** @return 文本邏輯行數(shù)*/public abstract int getLineNumber();/*** @param index 行號(hào)* @return 第index行的內(nèi)容*/public abstract String getLine(int index);/** 省略其它有用的方法*/ }還有B模塊(文本顯示模塊),它是這樣的:
package AdapterPattern;/*** @author ayqy* 文本顯示模塊*/ public class TextPrintModule {DefaultText text;public TextPrintModule(DefaultText text){this.text = text;}/** 省略很多顯示相關(guān)操作* */ }B模塊入口所需的DefaultText:
package AdapterPattern;/*** @author ayqy* 定義默認(rèn)文本*/ public interface DefaultText { String text = null;/*** @return 文本邏輯行數(shù)*/public abstract int getLineCount();/*** @param index 行號(hào)* @return 第index行的內(nèi)容*/public abstract String getLineContent(int index);/** 省略其它有用的方法*/ }我們的新項(xiàng)目要求實(shí)現(xiàn)一個(gè)文字處理程序(像MSOffice Word那樣的),我們需要調(diào)用A模塊來實(shí)現(xiàn)文本檢查功能,還需要調(diào)用B模塊來實(shí)現(xiàn)文本顯示功能
但問題是,兩個(gè)模塊的接口不匹配,導(dǎo)致我們無法直接復(fù)用現(xiàn)成的A和B。。
這時(shí)我們似乎只有有兩個(gè)選擇:
當(dāng)然,我們可能更傾向與第一種,畢竟所需的修改相對(duì)較少,不過即使這樣,工作量仍然很大,我們需要打開A的封裝,理解其內(nèi)部實(shí)現(xiàn),并修改方法調(diào)用細(xì)節(jié)
-------
其實(shí)我們還有更好的選擇——定義一個(gè)Adapter,負(fù)責(zé)FormatText到DefaultText的轉(zhuǎn)換(或者與此相反):
package AdapterPattern;/*** @author ayqy* 定義默認(rèn)文本適配器*/ public class DefaultTextAdapter implements DefaultText{FormatText formatText = null;//源對(duì)象/*** @param text 需要轉(zhuǎn)換的源文本對(duì)象*/public DefaultTextAdapter(FormatText formatText){this.formatText = formatText;}@Overridepublic int getLineCount() {int lineNumber;lineNumber = formatText.getLineNumber();/** 在此添加額外的轉(zhuǎn)換處理* */return lineNumber;}@Overridepublic String getLineContent(int index) {String line;line = formatText.getLine(index);/** 在此添加額外的轉(zhuǎn)換處理* */return line;} }我們的做法其實(shí)相當(dāng)簡(jiǎn)單:
適配器做好了,要怎么用呢?不妨實(shí)現(xiàn)一個(gè)Test類來測(cè)試一下:
package AdapterPattern;/*** @author ayqy* 測(cè)試接口適配器*/ public class Test implements FormatText{public static void main(String[] args) {//創(chuàng)建源接口對(duì)象FormatText text = new Test();//創(chuàng)建文本檢查模塊對(duì)象TextCheckModule tcm = new TextCheckModule(text);/*調(diào)用tcm實(shí)現(xiàn)文本檢查*///創(chuàng)建適配器對(duì)象,進(jìn)行源接口對(duì)象到目標(biāo)接口對(duì)象的轉(zhuǎn)換DefaultTextAdapter textAdapter = new DefaultTextAdapter(text);//用Adapter創(chuàng)建文本顯示模塊對(duì)象TextPrintModule tpm = new TextPrintModule(textAdapter);/*調(diào)用tcm實(shí)現(xiàn)文本顯示*/}/*請(qǐng)忽略下面偷懶的部分。。*/@Overridepublic int getLineNumber() {// TODO Auto-generated method stubreturn 0;}@Overridepublic String getLine(int index) {// TODO Auto-generated method stubreturn null;}}(P.S.原諒我的偷懶行為,誰讓FormatText偏偏是個(gè)接口呢。。)
當(dāng)然,Test是不會(huì)有運(yùn)行結(jié)果的,但能通過編譯就足夠說明我們的轉(zhuǎn)換沒有問題。。
-------
其實(shí)我們忽略了一個(gè)很重要的問題,例子中源接口與目標(biāo)接口的方法都是對(duì)應(yīng)的,換句話說就是:源接口中定義的方法在目標(biāo)接口中都有類似的方法與之對(duì)應(yīng)
當(dāng)然,這樣的情況是極少的,通常都存在方法不對(duì)應(yīng)的問題(源接口中存在目標(biāo)接口未定義的方法,或者相反的情況)
這時(shí)我們有2個(gè)選擇:
- 拋出異常,但應(yīng)該在注釋或者文檔作出詳細(xì)說明,就像這樣:
- 完成一個(gè)空的實(shí)現(xiàn),比如,return false,0,null等等
具體選擇哪一種,取決于具體情景,各有各的好處,不能一概而論
三.另一種適配器實(shí)現(xiàn)方式
例子中我們采用了“持有源接口對(duì)象,實(shí)現(xiàn)目標(biāo)接口”的方式來實(shí)現(xiàn)適配器,其實(shí)還存在另一種方式——多繼承(或者實(shí)現(xiàn)多個(gè)接口)
如果一個(gè)Adapter類既實(shí)現(xiàn)了A接口又實(shí)現(xiàn)了B接口,那么,毫無疑問,Adapter對(duì)象既屬于A類型又屬于B類型(多繼承的原理類似。。)
雖然Java不支持多繼承,但在支持多繼承的語言環(huán)境下我們應(yīng)當(dāng)想到這樣的實(shí)現(xiàn)方式,再視具體情況決定是否采用多繼承來實(shí)現(xiàn)Adapter
四.總結(jié)
當(dāng)我們手里同時(shí)握著一個(gè)兩孔插頭和一個(gè)三孔插口時(shí),總是習(xí)慣把插頭芯擰成八字形的。為什么不去買一個(gè)適配器呢?
- 既不需要破壞插頭,也不需要破壞插口(有時(shí)代碼修改確實(shí)是破壞性的,我們避免了修改也就避免了破壞)
- 更關(guān)鍵的是:我們可以把買來的適配器借給朋友用(可復(fù)用)
轉(zhuǎn)載于:https://www.cnblogs.com/ayqy/p/3971725.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的设计模式之适配器模式(Adapter Pattern)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Head first servlet
- 下一篇: 持续集成:CruiseControl.N