設計模式之【介面卡模式】

Gopher大威發表於2022-03-19

我們生活中很熟悉的轉換插頭,因為全球有幾種不同的插座介面標準,國內使用的插頭不一定適用於國外一些國家的插座,這時候,就需要用到轉換插頭了。我手中的這個就是德標轉國標的。

 

你看,這不就類似我們設計模式中的介面卡模式嘛?


將一個類的介面轉換成客戶希望的另外一個介面,使得原來由於介面不相容而不能一起工作的那些類可以一起工作。

文字描述不容易理解沒關係,通過下圖,應該會有一個更直觀的認識。

 

需求總是會不斷地變化,現在我們需要從舊廠商類切換到新廠商類,由於現有的系統與新廠商類彼此不相容,修改某一個類以相容另外一個類,都不是件輕鬆的事。這時候,就需要介面卡模式來“救場”了。你看,只需要增加一個介面卡的程式碼,就可以了,很輕鬆。

有了直觀的認識之後,我們就來好好地認識一下介面卡模式。

 

  • Target目標角色:該角色定義把其他類轉換為何種介面,也就是我們的期望介面。

  • Adaptee源角色:你想把誰轉換成目標角色,這個“誰”就是源角色,它是已經存在的,執行良好的類或物件,經過介面卡角色的包裝,它會成為一個嶄新的角色。

  • Adapter介面卡角色:介面卡模式的核心角色,其他兩個角色都是已經存在的角色,而介面卡角色是需要新建立的,它的職責非常簡單:就是把源角色轉換為目標角色。

介面卡模式有兩種實現方式:類介面卡物件介面卡。其中,類介面卡使用繼承關係來實現,物件介面卡使用組合關係來實現。

類介面卡

類介面卡的重點在於類,是通過構造一個繼承Adaptee類來實現介面卡的功能。

Adaptee:小明進入NBA了,但是他只會用中文溝通,

 1 // 已存在的,但是不符合我們既有的標準介面的類
 2 public class XiaoMing {
 3     private int Num;
 4     private String name;
 5     
 6     // 只會用中文溝通
 7     public void Chinese() {
 8         System.out.println("用中文溝通");
 9     }
10 }

Target:但是球隊教練是法國人,只會法語和英語,不用用中文。

1 // 目標介面,或稱為標準介面
2 public interface Coach {
3     void English();
4     void French();
5 }

Adapter:球隊給小明安排了一位翻譯。

 1 // 介面卡類:繼承了被適配類,同時實現了標準介面
 2 public class Interpreter extends XiaoMing implements Coach {
 3     
 4     @Override 
 5     public void English() {
 6         // 和球員小明用中文溝通
 7         super.Chinese();
 8         System.out.println("和教練用英語溝通");
 9     }
10     
11     @Override 
12     public void French() {
13         // 和球員小明用中文溝通
14         super.Chinese();
15         System.out.println("和教練用法語溝通");
16     }
17 }

客戶端

 1 public class Demo {
 2     public static void main(String[] args) {
 3         Coach interpreter = new Interpreter();
 4         
 5         // 翻譯是雙方溝通的橋樑
 6         interpreter.Chinese();
 7         interpreter.English();
 8         interpreter.French();
 9     }
10 }

但是,由於Java中不支援多繼承,所以這個介面卡(翻譯)只能服務於所繼承的被適配者(小明)。如果球隊裡有很多不會講英語和法語的球員,每個球員都安排一位翻譯的話,那成本就會很大,那有什麼辦法解決呢?

這個物件介面卡就派上用場了。

物件適配

物件介面卡的重點在於物件,是通過在直接組合Adaptee類來實現的,而不需要繼承被適配的類。而是通過在介面卡的建構函式中將需要被適配的類傳遞進來,從而進行適配。

Adapter程式碼如下,Target和Adaptee的程式碼同上。

 1 public class Interpreter implements Coach {
 2     // 與類介面卡不同的是,物件介面卡可以適配多個源到目標
 3     private Xiaoming xiaoming;
 4     // 比如,還可以組合其他Adaptee
 5     // private Tony tony;   // 講俄語
 6     
 7     // 在建構函式中將Adaptee類Xiaoming傳遞進來
 8     public Interpreter(Xiaoming xiaoming) {
 9         this.xiaoming = xiaoming;
10     }
11     
12     // 實現介面中的方法
13     @Override
14     public void English() {
15         // 和球員小明用中文溝通
16         this.xiaoming.Chinese();
17         // 和教練用英語溝通
18         System.out.println("用英語溝通");
19     }
20     
21     @Override 
22     public void French() {
23         // 和球員小明用中文溝通
24         this.xiaoming.Chinese();
25         // 和教練用法語溝通
26         System.out.println("用法語溝通");
27     }
28 }

客戶端

1 public class Demo {
2     public static void main(String[] args) {
3         Coach interpreter = new Interpreter(new XiaoMing());
4         
5         interpreter.English();
6         interpreter.French();
7     } 
8 }

兩種方式應該如何選擇?

判斷的標準主要有兩個,一個是Adaptee介面的個數,另一個是Adaptee和Target的契合程度。

  • 如果Adaptee介面並不多,那兩種實現方式是都可以;

  • 如果Adaptee介面很多,而且Adaptee和Target介面定義大部分都相同,那我們推薦使用類介面卡,因為Adapter複用父類Adaptee的介面,比起物件介面卡的實現方式,Adapter的程式碼量要少一些。

  • 如果Adaptee介面很多,而且Adaptee和Target介面定義大部分都不相同,那我們推薦使用物件介面卡,因為組合結構相對於繼承更加靈活。

介面卡模式的優點

  • 介面卡模式可以讓兩個沒有關係的類在一起執行,只要介面卡這個角色能夠搞定他們就可以。

  • 增加了類的透明性

    想想看,我們訪問的Target目標角色,但是具體的實現都委託給了源角色,而這些對高層次模組是透明的,也是它不需要關心的。

  • 提高了類的複用度

    源角色在原有的系統中還是可以正常使用的,而在目標角色中也可以充當新的演員。

  • 靈活性非常好

    如果突然不想要介面卡,沒有問題,刪除掉這個介面卡就可以了,其他的程式碼都不用修改,基本上就類似一個靈活的構件,想用就用,不用就解除安裝。

介面卡模式的缺點

  • 對於類介面卡來說,由於Java、C#等不支援多重繼承的語言,一次最多隻能適配一個適配者類,而且目標抽象類只能為介面,不能為類,其使用有一定的侷限性,不能將一個適配者類和它的子類同時適配到目標介面。

  • 對於物件介面卡來說,與類介面卡模式相比,要想置換適配者類的方法就不容易。

  • 過多地使用介面卡,會讓系統非常凌亂,不容易整體進行把握。

    比如,明明看到呼叫的是A介面,其實內部被適配成了B介面來實現,一個系統如果出現太多這種情況,無異於一場災難。

介面卡模式的應用場景

一般來說,介面卡模式可以看作是一種“補償模式”,用來補救設計上的缺陷。應用這種模式算是“無奈之舉”。如果在設計初期,就能協調規避介面不相容的問題,那麼,介面卡模式就沒有應用的機會了。

  1. 封裝有缺陷的介面設計

    如果我們依賴的外部系統在介面設計方面有缺陷(比如包含大量靜態方法),引入之後會影響到我們自身程式碼的可測試性。為了隔離設計上的缺陷,我們希望對外部系統提供的介面進行二次封裝,抽象出更好的介面設計,這個時候就可以使用介面卡模式了。

  2. 統一多個類的介面設計

    某個功能的實現依賴多個外部系統(或者說類)。通過介面卡模式,將它們的介面適配為統一的介面定義,然後我們就可以使用多型的特性來複用程式碼邏輯。

  3. 替換依賴的外部系統

    當我們把專案中依賴的一個外部系統替換為另一個外部系統的時候,利用介面卡模式,可以減少對程式碼的改動。

  4. 相容老版本介面

    在做版本升級的時候,對於一些要廢棄的介面,我們不直接將其刪除,而是暫時保留,並且標註為deprecated,並將內部實現邏輯委託為新的介面實現。這樣做的好處是,讓使用它的專案有個過渡期,而不是強制進行程式碼修改。這也可以粗略地看做介面卡模式的一個應用場景。

  5. 適配不同格式的資料

    介面卡模式主要用於介面的適配,實際上,它還可以用在不同格式的資料之間的適配。比如,把從不同徵信系統拉取的不同格式的徵信資料,統一為相同的格式,以方便儲存和使用。再比如,Java中的Arrays.asList()也可以看作一種資料介面卡,將資料型別的資料轉化為集合容器型別。

總結

介面卡模式主要應用於希望複用一些現存的類,但是介面又與複用環境要求不一致的情況。

參考

《設計模式之禪》

極客時間專欄《設計模式之美》

《Head First 設計模式》

https://www.jb51.net/article/100146.htm

 

相關文章