1. 程式人生 > >【23種設計模式】結構型模式 > 介面卡模式

【23種設計模式】結構型模式 > 介面卡模式

介面卡模式(Adapter Pattern)

用來消除不相容性。我的膝上型電腦的工作電壓是20V,而我國的家庭用電是220V,如何讓20V的膝上型電腦能夠在220V的電壓下工作?答案是引入一個電源介面卡(AC Adapter),俗稱充電器或變壓器,有了這個電源介面卡,生活用電和膝上型電腦即可相容(重要點在於,電腦工作電壓和民用電壓都不能被我們修改,所以我們引入了一個介面卡,在不修改電腦工作電壓和民用電壓的情況下,使得電腦獲得了20V 輸入電壓,這就是介面卡模式的關鍵,即被適配的兩方不能被修改)。在軟體開發中,有時也存在類似這種不相容的情況,我們也可以像引入一個電源介面卡一樣引入一個稱之為介面卡的角色來協調這些存在不相容的結構,這種設計方案即為介面卡模式。

一、介紹

介面卡模式有物件介面卡類介面卡兩種實現。在物件介面卡模式中,介面卡與適配者之間是關聯關係;在類介面卡模式中,介面卡與適配者之間是繼承(或實現)關係。在實際開發中,物件介面卡的使用頻率更高。

二、適用場景

      在以下情況下可以考慮使用介面卡模式:
       (1) 系統需要使用一些現有的類,而這些類的介面(如方法名)不符合系統的需要,甚至沒有這些類的原始碼。
       (2) 想建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。

三、物件介面卡

3.1 UML類圖


在物件介面卡模式結構圖中包含如下幾個角色:
       ● Target(目標抽象類)
:目標抽象類定義客戶所需介面,可以是一個抽象類或介面,也可以是具體類。
       ● Adapter(介面卡類)也被稱為包裝類:介面卡可以呼叫另一個介面,作為一個轉換器,對Adaptee和Target進行適配,介面卡類是介面卡模式的核心,在物件介面卡中,它通過繼承Target並關聯一個Adaptee物件使二者產生聯絡。
       ● Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經存在的介面,這個介面需要適配,適配者類一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的原始碼。
比如我在上家公司新寫了一個棋牌遊戲控制元件的類庫,相比公司原有的麻將控制元件更加靈活,我暴露出來的介面也和原先的介面很不一樣,但是由於舊的控制元件介面已經用在了很多地方,要全部改成新介面也不太現實,這時候我寫的新介面就需要去適配舊的介面。在這裡,我寫的新介面就是適配者類Adaptee,公司舊有的介面就是適配的目標Target,而我還需要寫一個介面卡Adapter,用新的介面來實現舊有的介面,也就是適配的目標。這樣公司的以前的程式碼就不需要修改介面,仍舊採用舊的呼叫形式,但是介面的實現是用我寫的新介面實現的。 介面卡模式的核心是Adapter(介面卡類)的編寫,典型的物件介面卡類是: C++ 
class Adapter : public Target // 繼承Target類
{
private :
    Adaptee adaptee; //維持一個對適配者物件的引用 ,關聯Adaptee類
public: 
    Adapter(Adaptee adaptee) {
        this->adaptee = adaptee;
    }

    void request() {
        adaptee.specificRequest(); //轉發呼叫  
    }
};

3.2 程式碼實現

C++ 
#include <iostream>
using namespace std; 

// 抽象類
class Target
{
public: 
    virtual ~Target() {};
    // 目標方法
    virtual void request() = 0; 
};

class Adaptee
{
public:
    // 被適配的方法
    void specificRequest()
    {
        cout << "specificRequest()" << endl;
    }
};

class Adapter : public Target
{
public:
    Adapter()
    {
        p = Adaptee();
    }
    // 目標方法
    void request()
    {
        // 呼叫被適配的方法
        p.specificRequest();
    }
private: 
    Adaptee p; 
};

int main()
{
    Target& t = Adapter();
    t.request();
}

C#
using System;
using System.Collections.Generic;
class Program
{
    interface Target
    {
        // 目標介面
        void request(); 
    }

    class Adaptee
    {
        // 被適配介面
        public void specificRequest()
        {
            Console.WriteLine("specificRequest()");
        }
    }

    class Adapter : Target
    {
        private Adaptee ad = new Adaptee();  // 關聯到Adaptee
        public void request()
        {
            ad.specificRequest();
        }
    }
    
    public static void Main()
    {
        Target t = new Adapter();
        t.request();
    }

} 
結果: specificRequest()

四、類介面卡

4.1 UML類圖


類介面卡和物件介面卡的組成元素一樣,唯一的區別是Adapter針對Adaptee,從關聯關係變成了繼承關係,並把Target寫成了介面(因為C#中類只能單繼承)。

4.2 程式碼實現

C++
#include <iostream>
using namespace std; 

// 抽象類
class Target
{
public: 
    virtual ~Target() {};
    // 目標方法
    virtual void request() = 0; 
};
class Adaptee
{
public:
    // 被適配的方法
    void specificRequest()
    {
        cout << "specificRequest()" << endl;
    }
};
class Adapter : public Target, public Adaptee // C++實現中只在Adapter類和物件介面卡有區別
{
public:
    Adapter()
    {
    }
    // 目標方法
    void request()
    {
        // 呼叫被適配的方法
        specificRequest();
    }
};

int main()
{
    Target& t = Adapter();
    t.request();
}
C#
using System;
using System.Collections.Generic;
class Program
{
    interface Target
    {
        // 目標介面
        void request(); 
    }

    class Adaptee
    {
        // 被適配介面
        public void specificRequest()
        {
            Console.WriteLine("specificRequest()");
        }
    }

    class Adapter : Adaptee, Target
    {
        public void request()
        {
            specificRequest();
        }
    }
    
    public static void Main()
    {
        Target t = new Adapter();
        t.request();
    }
}

五、物件介面卡和類介面卡的比較

類介面卡:
  •  用一個具體的Adapter類對Adaptee和Target進行匹配,結果是當我們想要匹配一個類以及所有他的子類的時候,類Adapter將不能勝任。
  •  使得Adapter可以重定義Adaptee的部分行為,因為Adapter是Adaptee的一個子類。
  •  僅僅引用了一個物件,並不需要額外的指標以間接得到Adapteee。
  •  類介面卡採用“多繼承”的實現方式,帶來了不良的高耦合,所以一般不推薦使用。
物件介面卡:
  •  允許一個Adapter與多個Adaptee(Adaptee本身和他的所有子類)同時工作,Adaptee也可以一次給所有的Adaptee新增功能。
  •  使得重定義Adaptee的行為比較困難。這就需要生成Adaptee的子類並且使得Adapter引用這個子類而不是引用Adaptee本身

六、優缺點

6.1 優點

       無論是物件介面卡模式還是類介面卡模式都具有如下優點:

       (1) 將目標類和適配者類解耦,通過引入一個介面卡類來重用現有的適配者類,無須修改原有結構。

       (2)增加了類的透明性和複用性,將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,而且提高了適配者的複用性,同一個適配者類可以在多個不同的系統中複用。

       (3) 靈活性和擴充套件性都非常好,通過使用配置檔案,可以很方便地更換介面卡,也可以在不修改原有程式碼的基礎上增加新的介面卡類,完全符合“開閉原則”。

      具體來說,類介面卡模式還有如下優點:

      由於介面卡類是適配者類的子類,因此可以在介面卡類中置換一些適配者的方法,使得介面卡的靈活性更強。

      物件介面卡模式還有如下優點:

      (1) 一個物件介面卡可以把多個不同的適配者適配到同一個目標

      (2) 可以適配一個適配者的子類,由於介面卡和適配者之間是關聯關係,根據“里氏代換原則”,適配者的子類也可通過該介面卡進行適配。


6.2 缺點

     類介面卡模式的缺點如下:

      (1) 對於JavaC#等不支援多重類繼承的語言,一次最多隻能適配一個適配者類,不能同時適配多個適配者

      (2) 適配者類不能為最終類,如在Java中不能為final類,C#中不能為sealed類;

      (3) JavaC#等語言中,類介面卡模式中的目標抽象類只能為介面,不能為類,其使用有一定的侷限性。

      物件介面卡模式的缺點如下:

      與類介面卡模式相比,要在介面卡中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個或多個方法,可以先做一個適配者類的子類,將適配者類的方法置換掉,然後再把適配者類的子類當做真正的適配者進行適配,實現過程較為複雜。