介面卡模式(Adapter Pattern)- 最易懂的設計模式解析
前言
今天我來全面總結一下Android開發中最常用的設計模式 - 介面卡模式。
目錄

1.jpg
1. 介紹
1.1 模式說明
定義一個包裝類,用於包裝不相容介面的物件
包裝類 = 介面卡Adapter;
被包裝物件 = 適配者Adaptee = 被適配的類
1.2 主要作用
把一個類的介面變換成客戶端所期待的另一種介面,從而使原本介面不匹配而無法一起工作的兩個類能夠在一起工作。
介面卡模式的形式分為:類的介面卡模式 & 物件的介面卡模式
1.3 解決的問題
原本由於介面不相容而不能一起工作的那些類可以在一起工作
2. 模式原理
2.1 類的介面卡模式
類的介面卡模式是把適配的類的API轉換成為目標類的API。
2.1.1 UML類圖 & 組成

944365-24c6bf44da1b79ad.png
在上圖中可以看出:
衝突:Target期待呼叫Request方法,而Adaptee並沒有(這就是所謂的不相容了)。
解決方案:為使Target能夠使用Adaptee類裡的SpecificRequest方法,故提供一箇中間環節Adapter類(繼承Adaptee & 實現Target介面),把Adaptee的API與Target的API銜接起來(適配)。
Adapter與Adaptee是繼承關係,這決定了這個介面卡模式是類的
2.1.2 使用步驟(程式碼解析)
步驟1: 建立Target介面;
public interface Target { //這是源類Adapteee沒有的方法 public void Request(); }
步驟2: 建立源類(Adaptee) ;
public class Adaptee { public void SpecificRequest(){ } }
步驟3: 建立介面卡類(Adapter)
//介面卡Adapter繼承自Adaptee,同時又實現了目標(Target)介面。 public class Adapter extends Adaptee implements Target { //目標介面要求呼叫Request()這個方法名,但源類Adaptee沒有方法Request() //因此介面卡補充上這個方法名 //但實際上Request()只是呼叫源類Adaptee的SpecificRequest()方法的內容 //所以介面卡只是將SpecificRequest()方法作了一層封裝,封裝成Target可以呼叫的Request()而已 @Override public void Request() { this.SpecificRequest(); } }
步驟4:定義具體使用目標類,並通過Adapter類呼叫所需要的方法從而實現目標
public class AdapterPattern { public static void main(String[] args){ Target mAdapter = new Adapter(); mAdapter.Request(); } }
2.1.3 例項講解
接下來我用一個例項來對類的介面卡模式進行更深一步的介紹。
a. 例項概況
背景:小成買了一個進口的電視機
衝突:進口電視機要求電壓(110V)與國內插頭標準輸出電壓(220V)不相容
解決方案:設定一個介面卡將插頭輸出的220V轉變成110V
即介面卡模式中的類的介面卡模式
b. 使用步驟
步驟1: 建立Target介面(期待得到的插頭):能輸出110V(將220V轉換成110V)
public interface Target { //將220V轉換輸出110V(原有插頭(Adaptee)沒有的) public void Convert_110v(); }
步驟2: 建立源類(原有的插頭) ;
class PowerPort220V{ //原有插頭只能輸出220V public void Output_220v(){ } }
步驟3:建立介面卡類(Adapter)
class Adapter220V extends PowerPort220V implements Target{ //期待的插頭要求呼叫Convert_110v(),但原有插頭沒有 //因此介面卡補充上這個方法名 //但實際上Convert_110v()只是呼叫原有插頭的Output_220v()方法的內容 //所以介面卡只是將Output_220v()作了一層封裝,封裝成Target可以呼叫的Convert_110v()而已 @Override public void Convert_110v(){ this.Output_220v; } }
步驟4:定義具體使用目標類,並通過Adapter類呼叫所需要的方法從而實現目標(不需要通過原有插頭)
//進口機器類 class ImportedMachine { @Override public void Work() { System.out.println("進口機器正常執行"); } } //通過Adapter類從而呼叫所需要的方法 public class AdapterPattern { public static void main(String[] args){ Target mAdapter220V = new Adapter220V(); ImportedMachine mImportedMachine = new ImportedMachine(); //使用者拿著進口機器插上介面卡(呼叫Convert_110v()方法) //再將介面卡插上原有插頭(Convert_110v()方法內部呼叫Output_220v()方法輸出220V) //介面卡只是個外殼,對外提供110V,但本質還是220V進行供電 mAdapter220V.Convert_110v(); mImportedMachine.Work(); } }
2.2 物件的介面卡模式
與類的介面卡模式相同,物件的介面卡模式也是把適配的類的API轉換成為目標類的API。
與類的介面卡模式不同的是,物件的介面卡模式不是使用繼承關係連線到Adaptee類,而是使用委派關係連線到Adaptee類。
2.2.1 UML類圖

944365-24c6bf44da1b79ad.png
在上圖中可以看出:
衝突:Target期待呼叫Request方法,而Adaptee並沒有(這就是所謂的不相容了)。
解決方案:為使Target能夠使用Adaptee類裡的SpecificRequest方法,故提供一箇中間環節Adapter類(包裝了一個Adaptee的例項),把Adaptee的API與Target的API銜接起來(適配)。
Adapter與Adaptee是委派關係,這決定了介面卡模式是物件的。
2.2.2 使用步驟(程式碼解析)
步驟1: 建立Target介面;
public interface Target { //這是源類Adapteee沒有的方法 public void Request(); }
步驟2: 建立源類(Adaptee) ;
public class Adaptee { public void SpecificRequest(){ } }
步驟3: 建立介面卡類(Adapter)(不適用繼承而是委派)
class Adapter implements Target{ // 直接關聯被適配類 private Adaptee adaptee; // 可以通過建構函式傳入具體需要適配的被適配類物件 public Adapter (Adaptee adaptee) { this.adaptee = adaptee; } @Override public void Request() { // 這裡是使用委託的方式完成特殊功能 this.adaptee.SpecificRequest(); } }
步驟4:定義具體使用目標類,並通過Adapter類呼叫所需要的方法從而實現目標
public class AdapterPattern { public static void main(String[] args){ //需要先建立一個被適配類的物件作為引數 Target mAdapter = new Adapter(new Adaptee()); mAdapter.Request(); } }
在這裡我就不再舉例項進行講解了(詳情請看上面“進口機器的插頭”),只是在適配類實現時將“繼承”改成“在內部委派Adaptee類”而已
3. 優缺點
3.1 介面卡模式
優點
- 更好的複用性
系統需要使用現有的類,而此類的介面不符合系統的需要。那麼通過介面卡模式就可以讓這些功能得到更好的複用。 - 透明、簡單
客戶端可以呼叫同一介面,因而對客戶端來說是透明的。這樣做更簡單 & 更直接 - 更好的擴充套件性
在實現介面卡功能的時候,可以呼叫自己開發的功能,從而自然地擴充套件系統的功能。 - 解耦性
將目標類和適配者類解耦,通過引入一個介面卡類重用現有的適配者類,而無需修改原有程式碼 - 符合開放-關閉原則
同一個介面卡可以把適配者類和它的子類都適配到目標介面;可以為不同的目標介面實現不同的介面卡,而不需要修改待適配類
缺點
過多的使用介面卡,會讓系統非常零亂,不易整體進行把握
3.2 類的介面卡模式
優點
使用方便,程式碼簡化
僅僅引入一個物件,並不需要額外的欄位來引用Adaptee例項
缺點
高耦合,靈活性低
使用物件繼承的方式,是靜態的定義方式
3.3 物件的介面卡模式
優點
靈活性高、低耦合
採用 “物件組合”的方式,是動態組合方式
缺點
使用複雜
需要引入物件例項
特別是需要重新定義Adaptee行為時需要重新定義Adaptee的子類,並將介面卡組合適配
4. 應用場景
4.1 介面卡的使用場景
- 系統需要複用現有類,而該類的介面不符合系統的需求,可以使用介面卡模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作
- 多個元件功能類似,但介面不統一且可能會經常切換時,可使用介面卡模式,使得客戶端可以以統一的介面使用它們
4.2 類和物件介面卡模式的使用場景
- 靈活使用時:選擇物件的介面卡模式
類介面卡使用物件繼承的方式,是靜態的定義方式;而物件介面卡使用物件組合的方式,是動態組合的方式。 - 需要同時配源類和其子類:選擇物件的介面卡
- 對於類介面卡,由於介面卡直接繼承了Adaptee,使得介面卡不能和Adaptee的子類一起工作,因為繼承是靜態的關係,當介面卡繼承了Adaptee後,就不可能再去處理 Adaptee的子類了;
- 對於物件介面卡,一個介面卡可以把多種不同的源適配到同一個目標。換言之,同一個介面卡可以把源類和它的子類都適配到目標介面。因為物件介面卡採用的是物件組合的關係,只要物件型別正確,是不是子類都無所謂。
- 需要重新定義Adaptee的部分行為:選擇類介面卡
- 對於類介面卡,介面卡可以重定義Adaptee的部分行為,相當於子類覆蓋父類的部分實現方法。
- 對於物件介面卡,要重定義Adaptee的行為比較困難,這種情況下,需要定義Adaptee的子類來實現重定義,然後讓介面卡組合子類。雖然重定義Adaptee的行為比較困難,但是想要增加一些新的行為則方便的很,而且新增加的行為可同時適用於所有的源。
- 僅僅希望使用方便時:選擇類介面卡
- 對於類介面卡,僅僅引入了一個物件,並不需要額外的引用來間接得到Adaptee。
- 對於物件介面卡,需要額外的引用來間接得到Adaptee。
總結
建議儘量使用物件的介面卡模式,多用合成/聚合、少用繼承。
當然,具體問題具體分析,根據需要來選用合適的實現方式