設計模式--結構性模式--介面卡模式(Adapter模式)
在現實生活中,經常出現兩個物件因介面不相容而不能在一起工作的例項,這時需要第三者進行適配。例如,講中文的人同講英文的人對話時需要一個翻譯,用直流電的膝上型電腦接交流電源時需要一個電源介面卡,用計算機訪問照相機的 SD 記憶體卡時需要一個讀卡器等。
在軟體設計中也可能出現:需要開發的具有某種業務功能的元件在現有的元件庫中已經存在,但它們與當前系統的介面規範不相容,如果重新開發這些元件成本又很高,這時用介面卡模式能很好地解決這些問題。
模式的定義與特點
介面卡模式(Adapter)的定義如下:將一個類的介面轉換成客戶希望的另外一個介面,使得原本由於介面不相容而不能一起工作的那些類能一起工作。介面卡模式分為類結構型模式
該模式的主要優點如下。
- 客戶端通過介面卡可以透明地呼叫目標介面。
- 複用了現存的類,程式設計師不需要修改原有程式碼而重用現有的適配者類。
- 將目標類和適配者類解耦,解決了目標類和適配者類介面不一致的問題。
- 在很多業務場景中符合開閉原則。
其缺點是:
- 介面卡編寫過程需要結合業務場景全面考慮,可能會增加系統的複雜性。
- 增加程式碼閱讀難度,降低程式碼可讀性,過多使用介面卡會使系統程式碼變得凌亂。
模式的結構與實現
類介面卡模式可採用多重繼承方式實現,如C++可定義一個介面卡類來同時繼承當前系統的業務介面和現有元件庫中已經存在的元件介面;
物件介面卡模式可釆用將現有元件庫中已經實現的元件引入介面卡類中,該類同時實現當前系統的業務介面。現在來介紹它們的基本結構。
1. 模式的結構
介面卡模式(Adapter)包含以下主要角色。
- 目標(Target)介面:當前系統業務所期待的介面,它可以是抽象類或介面。
- 適配者(Adaptee)類:它是被訪問和適配的現存元件庫中的元件介面。
- 介面卡(Adapter)類:它是一個轉換器,通過繼承或引用適配者的物件,把適配者介面轉換成目標介面,讓客戶按目標介面的格式訪問適配者。
類介面卡模式的結構圖如圖 1 所示。
物件介面卡模式的結構圖如圖 2 所示。
2. 模式的實現
(1) 類介面卡模式的程式碼如下。
package adapter; //目標介面 interface Target { public void request(); } //適配者介面 class Adaptee { public void specificRequest() { System.out.println("適配者中的業務程式碼被呼叫!"); } } //類介面卡類 class ClassAdapter extends Adaptee implements Target { public void request() { specificRequest(); } } //客戶端程式碼 public class ClassAdapterTest { public static void main(String[] args) { System.out.println("類介面卡模式測試:"); Target target = new ClassAdapter(); target.request(); } }
程式的執行結果如下:
類介面卡模式測試:
適配者中的業務程式碼被呼叫!
(2)物件介面卡模式的程式碼如下。
package adapter; //物件介面卡類 class ObjectAdapter implements Target { private Adaptee adaptee; public ObjectAdapter(Adaptee adaptee) { this.adaptee=adaptee; } public void request() { adaptee.specificRequest(); } } //客戶端程式碼 public class ObjectAdapterTest { public static void main(String[] args) { System.out.println("物件介面卡模式測試:"); Adaptee adaptee = new Adaptee(); Target target = new ObjectAdapter(adaptee); target.request(); } }
說明:物件介面卡模式中的“目標介面”和“適配者類”的程式碼同類介面卡模式一樣,只要修改介面卡類和客戶端的程式碼即可。
程式的執行結果如下:
物件介面卡模式測試:
適配者中的業務程式碼被呼叫!
模式的應用例項
【例1】用介面卡模式(Adapter)模擬新能源汽車的發動機。
分析:新能源汽車的發動機有電能發動機(Electric Motor)和光能發動機(Optical Motor)等,各種發動機的驅動方法不同,例如,電能發動機的驅動方法 electricDrive() 是用電能驅動,而光能發動機的驅動方法 opticalDrive() 是用光能驅動,它們是介面卡模式中被訪問的適配者。
客戶端希望用統一的發動機驅動方法 drive() 訪問這兩種發動機,所以必須定義一個統一的目標介面 Motor,然後再定義電能介面卡(Electric Adapter)和光能介面卡(Optical Adapter)去適配這兩種發動機。
我們把客戶端想訪問的新能源發動機的介面卡的名稱放在 XML 配置檔案中(點此下載 XML 檔案),客戶端可以通過物件生成器類 ReadXML 去讀取。這樣,客戶端就可以通過 Motor 介面隨便使用任意一種新能源發動機去驅動汽車,圖 3 所示是其結構圖。
程式程式碼如下:
package adapter; //目標:發動機 interface Motor { public void drive(); } //適配者1:電能發動機 class ElectricMotor { public void electricDrive() { System.out.println("電能發動機驅動汽車!"); } } //適配者2:光能發動機 class OpticalMotor { public void opticalDrive() { System.out.println("光能發動機驅動汽車!"); } } //電能介面卡 class ElectricAdapter implements Motor { private ElectricMotor emotor; public ElectricAdapter() { emotor=new ElectricMotor(); } public void drive() { emotor.electricDrive(); } } //光能介面卡 class OpticalAdapter implements Motor { private OpticalMotor omotor; public OpticalAdapter() { omotor=new OpticalMotor(); } public void drive() { omotor.opticalDrive(); } } //客戶端程式碼 public class MotorAdapterTest { public static void main(String[] args) { System.out.println("介面卡模式測試:"); Motor motor=(Motor)ReadXML.getObject(); motor.drive(); } }
package adapter; import javax.xml.parsers.*; import org.w3c.dom.*; import java.io.*; class ReadXML { public static Object getObject() { try { DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance(); DocumentBuilder builder=dFactory.newDocumentBuilder(); Document doc; doc=builder.parse(new File("src/adapter/config.xml")); NodeList nl=doc.getElementsByTagName("className"); Node classNode=nl.item(0).getFirstChild(); String cName="adapter."+classNode.getNodeValue(); Class<?> c=Class.forName(cName); Object obj=c.newInstance(); return obj; } catch(Exception e) { e.printStackTrace(); return null; } } }
程式的執行結果如下:
介面卡模式測試:
電能發動機驅動汽車!
注意:如果將配置檔案中的 ElectricAdapter 改為 OpticalAdapter,則執行結果如下:
介面卡模式測試:
光能發動機驅動汽車!
模式的應用場景
介面卡模式(Adapter)通常適用於以下場景。
- 以前開發的系統存在滿足新系統功能需求的類,但其介面同新系統的介面不一致。
- 使用第三方提供的元件,但元件介面定義和自己要求的介面定義不同。
模式的擴充套件
介面卡模式(Adapter)可擴充套件為雙向介面卡模式,雙向介面卡類既可以把適配者介面轉換成目標介面,也可以把目標介面轉換成適配者介面,其結構圖如圖 4 所示。
程式程式碼如下:
package adapter; //目標介面 interface TwoWayTarget { public void request(); } //適配者介面 interface TwoWayAdaptee { public void specificRequest(); } //目標實現 class TargetRealize implements TwoWayTarget { public void request() { System.out.println("目的碼被呼叫!"); } } //適配者實現 class AdapteeRealize implements TwoWayAdaptee { public void specificRequest() { System.out.println("適配者程式碼被呼叫!"); } } //雙向介面卡 class TwoWayAdapter implements TwoWayTarget,TwoWayAdaptee { private TwoWayTarget target; private TwoWayAdaptee adaptee; public TwoWayAdapter(TwoWayTarget target) { this.target=target; } public TwoWayAdapter(TwoWayAdaptee adaptee) { this.adaptee=adaptee; } public void request() { adaptee.specificRequest(); } public void specificRequest() { target.request(); } } //客戶端程式碼 public class TwoWayAdapterTest { public static void main(String[] args) { System.out.println("目標通過雙向介面卡訪問適配者:"); TwoWayAdaptee adaptee=new AdapteeRealize(); TwoWayTarget target=new TwoWayAdapter(adaptee); target.request(); System.out.println("-------------------"); System.out.println("適配者通過雙向介面卡訪問目標:"); target=new TargetRealize(); adaptee=new TwoWayAdapter(target); adaptee.specificRequest(); } }
程式的執行結果如下:
目標通過雙向介面卡訪問適配者: 適配者程式碼被呼叫! ------------------- 適配者通過雙向介面卡訪問目標: 目的碼被呼叫!