1. 程式人生 > 實用技巧 >【設計模式(六)】介面卡模式

【設計模式(六)】介面卡模式

個人學習筆記分享,當前能力有限,請勿貶低,菜鳥互學,大佬繞道

如有勘誤,歡迎指出和討論,本文後期也會進行修正和補充


前言

將一個類的介面轉成客戶期望的另外一個介面。介面卡模式使得原本由於介面不匹配而不能一起工作的那些類可以一起工作。——Gang of Four

介面卡英文為adapter,如果你是一位Android開發工程師,一定不會對這個單詞陌生,在Android開發中,我們經常需要設計介面卡,對資料加以處理使其以合適的形式在view中展示出來。簡而言之就是資料原先無法以合適的形式展示,我們需要介面卡來處理。

在生活中,我們最常見的介面卡其實就是電源介面卡(當然還有其他的),也就是usb充電器的插頭、電器的電源等。因為usb線無法直接使用插座上的插口,我們需要一個插頭(介面卡)來處理一下,就可以了。

原本不匹配而無法使用的介面,我們通過介面卡處理後,使其能夠正常匹配

最常見的,jdbc就是我一種介面卡,負責將資料庫的資料來源進行處理,最終以api的形式提供給Java客戶端使用,無論我們是使用mybatis還是mybatis-plus最終都無法繞過jdbc,因為這就是我們Java客戶端與資料來源的介面卡

實際上我們的應用開發中會使用到各種各樣的控制元件,以及各種各樣的資源,我們不可能拒絕使用與我們規範不同的資源(蘋果&Android:?有被冒犯),介面卡就是此種情況下的一種解決方案。

實際上就算是在統一規範下,也可能隨著規範的升級迭代而產生差異,此時也可以通過介面卡對其進行修正。


請注意區分適配和擴充套件,適配僅做轉換,已保證目標類可以被使用,而不生成新的功能

,新的功能是擴充套件,建議直接繼承進行擴充套件


1.介紹

使用目的:將一個類的介面轉換成被期望的另外一個介面,使得原本由於介面不相容而不能一起工作的那些類可以一起工作。

使用時機:需要的類介面不符合需求無法使用,或者需要使用的多個類沒有統一格式的介面無法一起使用

解決問題:原有的介面不符合需求

分類:根據適配的目標不同分為兩種

  • 介面卡模式
  • 物件介面卡模式

其實還有一種預設介面卡模式,本質是屬於介面卡模式的變型,本文後面會介紹

實現方法

  • 類介面卡模式:採用多重繼承方式實現,定義一個介面卡類來實現當前系統的業務介面,同時又繼承現有元件庫中已經存在的元件
  • 物件介面卡模式:釆用將現有元件庫中已經實現的元件引入介面卡類中,該類同時實現當前系統的業務介面。

預設適配模式為一個介面提供預設實現,這樣子型別可以從這個預設實現進行擴充套件,而不必從原有介面進行擴充套件

應用例項:

  • JDBC:JDBC連線資料來源(通常為資料庫),並提供API允許客戶端訪問資料庫,從而使客戶端能夠對資料庫進行增刪改查等操作
  • Android中的ListView、RecycleView等元件,都需要配合介面卡使用,介面卡負責將資料轉換為能夠展示的view
  • 對接硬體裝置(如攝像機),廠商會提供SDK,但大多數時候裡面的API介面無法直接使用,比如自己編寫介面卡對介面進行呼叫

優點

  1. 可以讓兩個沒有關聯的類一起執行:這也是初衷
  2. 提高了類的複用性:我們可以在介面卡中對功能進行改裝,使原本的功能更加完善甚至是創造新的功能
  3. 增加了類的透明度:有了介面卡客戶端就可以間接呼叫相關介面了
  4. 靈活度高:我們可以在介面卡中適當自己調整功能

缺點

  1. 由於Java中最多繼承一個類,所以一個介面卡至多隻能適配一個目標
  2. 實現複雜,過度使用會導致系統混亂,且維護成本高,某些時候可能還不如重構系統

注意事項:介面卡是為了解決一個新的類或物件介面與原系統不匹配的情況,而非設計系統架構時的元件。

換言之,在引入新的類或物件的介面不相容時使用介面卡模式,而在設計系統時的相容性問題直接調整架構解決即可


2.結構

介面卡模式包括四個主要角色:

  • 目標(Target)介面:即被期望的介面,可以是抽象類,或者介面
  • 適配者(Adaptee)介面:被訪問和適配的元件的介面
  • 介面卡(Adapter)類:轉換器,負責將適配者介面轉換為目標介面
  • 客戶端(Client):與符合Target介面的物件協同

3.類介面卡

類介面卡需要繼承適配者

理論上一般是多重繼承,即同時繼承兩個或多個適配者類,從而將其介面進行匹配

Java只允許單重繼承,所以只能繼承單個適配者類,並將其轉換為所需要的的介面,一般同時使用介面類(Interface)進行規範

簡而言之,Java只能將一個類的介面轉換為目標,而c/c++可以將兩個類的介面聚合在一起


3.1.結構

  • 客戶端使用目標介面
  • 目標介面實際上使用的是介面卡中的介面
  • 介面卡需要繼承適配者類,才能使用其內部的功能
  • 同時介面卡需要實現介面中的方法,作為目標介面使用

3.2.實現

這裡舉兩個例子,分別是兩種情況,實質上都是對原功能的擴充套件

請注意這裡有原功能才能擴充套件,沒有原功能的話屬於新增,跟介面卡沒有任何關係,這種需求請重新寫一個類

3.2.1.示例1

模擬業務如下:

  • 舊版本的類使用hello()方法打招呼
  • 新版本打算使用greet()方法打招呼,其業務相同

此時需要將hello()轉換為greet()方法


Adaptee適配者類(Adaptee)

package com.company.test.adapter;

public class Adaptee {
    public void hello() {
        System.out.println("HelloWorld!");
    }
}

適配者類,包括一個原方法,列印HelloWorld

請注意此類屬於原元件,不屬於介面卡的一部分,而是介面卡的目標


CusInterface自定義介面(Target)

package com.company.test.adapter;

public interface CusInterface {
    public void greet();
}

定義介面,用於規範目標介面


ClassAdapter介面卡類(Adapter)

package com.company.test.adapter;

public class ClassAdapter extends Adaptee implements CusInterface {
    @Override
    public void greet() {
        super.hello();
    }
}

此處繼承適配者類,並實現介面,在此處實現介面的轉換


Client客戶端類(Client)

package com.company.test.adapter;

public class ClassAdapterTest {
    public static void main(String[] args) {
        CusInterface adapter=new ClassAdapter();
        adapter.greet();
    }
}

執行結果


3.2.2.示例2

模擬業務如下:

  • 原元件提供加法介面int plus(int x,int y),即將兩個數字相加
  • 現在要求目標介面實現加法String plus(String x,String y),即數字以字串形式傳遞

此時需要對舊的介面進行轉換,使其達到目標介面效果

僅給出示例程式碼,不再解釋

Adaptee

package com.company.test.adapter.test2;

public class Adaptee {
    public int plus(int x, int y) {
        return x + y;
    }
}

CusInterface(Target)

package com.company.test.adapter.test2;

public interface CusInterface {
    String plus(String x, String y);
}

ObjectAdapter(Adapter)

package com.company.test.adapter.test2;

public class ObjectAdapter extends Adaptee implements CusInterface {
    @Override
    public String plus(String x, String y) {
        return String.valueOf(super.plus(Integer.valueOf(x), Integer.valueOf(y)));
    }
}

ObjectAdapterTest(Client)

package com.company.test.adapter.test2;

public class ClassAdapterTest {
    public static void main(String[] args) {
        CusInterface adapter = new ObjectAdapter();
        String ans = adapter.plus("111", "233");
        System.out.println(ans);
    }
}

執行結果

3.3.小結

介面卡通過繼承適配者,對舊介面進行轉換,通常是改變引數型別以使其匹配,通過super即可直接呼叫舊介面


4.物件介面卡

物件介面卡直接持有適配者類的例項物件

物件持有數量沒有限制,所以就沒有適配者數量的限制,也就可以將多個適配者同時進行適配

因而物件介面卡實際使用中遠遠多於類介面卡

4.1.結構

  • Adapter直接持有Adaptee物件,從而可以使用原元件的功能
  • Adapter裡的方法直接呼叫Adaptee物件的方法,進而對其實現轉換
  • 一個Adapter也可以持有多個Adaptee,進而適配多個適配者

4.2.實現

4.2.1.例項1

這裡只修改介面卡,其餘程式碼一致,便於比(tou)較(lan)

ClassAdapter

public class ClassAdapter implements CusInterface {
    private Adaptee adaptee=new Adaptee();
    @Override
    public void greet() {
        adaptee.hello();
    }
}

4.2.2.例項2

同樣只修改介面卡

ObjectAdapter

public class ObjectAdapter implements CusInterface {
    private Adaptee adaptee = new Adaptee();

    @Override
    public String plus(String x, String y) {
        return String.valueOf(adaptee.plus(Integer.valueOf(x), Integer.valueOf(y)));
    }
}

這裡持有適配者物件的方法有三種

  • 建立區域性變數,直接初始化一個適配者物件

    即上面使用的這種

  • 建立區域性變數,在建構函式中自行初始化

  • 建立區域性變數,通過建構函式從客戶端中接收適配者物件

4.2.3.例項3

我們再測試一下同時適配多個類的情況

  • 現有兩個播放元件,分別能夠播放視訊和音訊
  • 需要一個播放介面,能夠播放視訊和音訊

Adaptee

package com.company.test.adapter.test3;

public class VideoPlayer {
    public void play(String fileName)  {
        //todo play the audit
        System.out.println("play video :" + fileName);
    }
}
package com.company.test.adapter.test3;

public class AuditPlayer {
    public void play(String fileName){
        //todo play the audit
        System.out.println("play audit :"+fileName);
    }
}

Target

package com.company.test.adapter.test3;

public interface CusInterface {
    void play(String fileName);
}

Adapter

package com.company.test.adapter.test3;

public class ObjectAdapter implements CusInterface {
    private AuditPlayer auditPlayer = new AuditPlayer();
    private VideoPlayer videoPlayer = new VideoPlayer();

    @Override
    public void play(String fileName) {
        if (fileName.endsWith(".mp3")) {
            auditPlayer.play(fileName);
        } else if (fileName.endsWith(".mp4") || fileName.endsWith(".vcl")) {
            videoPlayer.play(fileName);
        } else {
            System.out.println("error:cannot recognize file " + fileName);
        }
    }
}

Client

package com.company.test.adapter.test3;

public class ObjectAdapterTest {
    public static void main(String[] args) {
        CusInterface adapter = new ObjectAdapter();
        adapter.play("hello.mp3");
        adapter.play("hello.mp4");
        adapter.play("hello.mp5");
    }
}

執行結果

4.3.小結

介面卡直接持有適配者類,在介面卡中定義所需要的的方法,根據需求使用合適的調整,可以直接使用適配者類中的方法


5.預設介面卡模式

預設適配(Default Adapter)模式為一個介面提供預設實現,這樣子型別可以從這個預設實現進行擴充套件,而不必從原有介面進行擴充套件。

即,如果可能需要很多功能,但當前只實現了一部分,那麼可以將未實現的功能預設實現

因為不這麼做連編譯都過不去。。。

作為介面卡模式的一個特例(或者說是變型),預設是適配模式在JAVA語言中有著特殊的應用

嚴格意義上,預設介面卡不屬於介面卡模式

5.1.結構

  • 需要有一個介面類interface,來規範全部的功能
  • 介面卡adapter對介面的所有方法進行空實現,或者其餘預設實現
  • 目標類target整合adapter,重寫那些需要實現的功能

因此target未實現的功能,就由adapter提供空實現或者預設實現啦(實際應用不建議空實現,彆強迫後人非得一行一行看你程式碼。。有些缺德。。)


5.2.實現

程式碼很簡單,我就不分開貼了

package com.company.test.adapter.test4;

interface InterfaceTarget {
    void sayHi();

    void sayHello();

    void sayBye();
}

class Adapter implements InterfaceTarget {

    @Override
    public void sayHi() {
    }

    @Override
    public void sayHello() {
    }

    @Override
    public void sayBye() {
        System.out.println("you need to override this function first!");
    }
}

class Target extends Adapter {
    @Override
    public void sayHi() {
        System.out.println("Hi!");
    }
}

public class DefaultAdapterTest {
    public static void main(String[] args) {
        InterfaceTarget target = new Target();
        target.sayHi();
        target.sayHello();
        target.sayBye();
    }
}
  1. 定義介面類InterfaceTarget,規範和提供所有介面

  2. 定義介面卡Adapter,實現介面類,並將所有介面進行空實現或者預設實現

    示例中sayBye()方法進行了預設實現,其餘兩個進行了空實現

  3. 定義目標類Target,繼承介面卡,並重寫我們實際實現的功能

  4. 客戶端就可以直接使用啦~所有方法都不會報錯

執行結果

5.3.小結

實際上預設介面卡模式就是提供了介面的預設實現而已,這樣就保證所有方法都被實現了,目標類就不會報錯了

簡而言之,在我們不需要實現所有方法時,使用預設介面卡,給出平庸實現


6.總結

6.1.不同介面卡的優缺點

類介面卡

  • 優點:由於介面卡是適配者的子類,因此可以在介面卡中重寫部分方法,使得介面卡靈活性更強

    但請注意,這種操作並不屬於介面卡的範疇,請不要將實際應用與理論基礎混淆

  • 缺點

    • 對於Java等不支援多重類繼承的語言,一次最多隻能適配一個適配者類,不能同時適配多個適配者;

    • 適配者類不能為最終類,即Java中的final類;

    • 類介面卡模式中的目標抽象類只能為介面,不能為類,其使用有一定的侷限性

      若使用類,那麼構造目標類的時候不會初始化子類

物件介面卡

  • 優點

    • 可以同時適配多個適配者類

    • 介面卡直接持有適配者類,那麼根據“里氏代換原則”,介面卡對適配者的子類同樣適用

      介面卡如果從客戶端接收適配者物件,那麼同樣可以接受適配者子類物件

  • 缺點:如果需要置換掉適配者中的方法,過程較為複雜

    類介面卡直接在介面卡中重寫適配者的方法即可

    物件介面卡需要先建立適配者的子類,重寫方法,再將介面卡中的物件更換為其子類


6.2.介面卡的意義

雖然目的都是為了讓目標類能夠與當前環境或者其他類進行匹配,但其用意不同

  • 類/物件介面卡是為了改變源介面,使其能夠與系統相容
  • 預設介面卡是為了讓未完全實現的介面,與系統相容(其實就是為了不報錯。。。)

後記

設計模式是總結的經驗,並非需要嚴格遵守的方案

實際使用中更多的是使用變型和衍生方案

例如介面卡模式實際使用中很少完全符合方案,也因此導致很多前輩總結的時候會將變型誤解為設計模式本身(查閱的資料和部落格很多地方都出現基於實踐的誤解和偏差)

雖然實戰中未必嚴格遵守方案,但除去根據需求的變更,應儘量符合設計模式的方案



作者:Echo_Ye

WX:Echo_YeZ

EMAIL :[email protected]

個人站點:在搭了在搭了。。。(右鍵 - 新建資料夾)