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