人人都會設計模式:04-介面卡模式–Adapter

tigerchain發表於2017-11-24

介面卡模式大綱

版權宣告:本文為博主原創文章,未經博主允許不得轉載
公眾號:TigerChain 新增公號更多文章等著你

作者: TigerChain

教程簡介

  • 1、閱讀物件
    本篇教程適合新手閱讀,老手直接略過
  • 2、教程難度
    初級,本人水平有限,文章內容難免會出現問題,如果有問題歡迎指出,謝謝

正文

一、什麼是介面卡模式

1、生活中的介面卡

比如電腦轉接器「這裡主要是指連線電腦和投影儀的」,以我的 MAC 電腦為例子,我們公司的投影支援 VGA 和 HDMI ,但是我的 MAC 電腦只有一個 MINI DP 介面,如何把 MINI DP 轉成 VGA 或 HDMI ,那麼我就買了這個玩意「全稱 MINI DP 轉 VGA & HDMI 介面卡」,這東西就是一個介面卡

MINI DP 轉 VGA 或 HDMI

這個介面卡就可以把 MAC 和有 VGA 或 HDMI 的裝置連線起來了,如下:

介面卡連線各個裝置

類似的還有電腦電源介面卡,變壓器「也是一種介面卡」,其實淨水器也可以看作是一種介面卡「把雜水變成純淨水」,等等

2、程式中的介面卡

比如我們對接第三方的介面到我們的系統「對方給我們的介面,我們現在的介面對接不起來」

我們介面和三方介面

我們就需要寫一箇中間層「介面卡」,做為一個橋樑,把兩個介面連線起來

介面或系統之間的介面卡

介面卡模式的定義

通俗的說介面卡模式就是把兩個不相容的介面連線起來,類似一個橋樑的作用

介面卡模式類比

注:介面卡模式類比一個橋樑作用「它的作用不僅僅連線這麼簡單,還有轉化等操作,橋樑就是為了方便理解」

介面卡模式的結構

角色 類別 說明
Target 目標角色 是一個介面,也就是我們期待要轉化成的介面
Adaptee 源角色 原始的類或介面物件
Adapter 介面卡角色 把源角色轉化成目標角色的類

介面卡模式的分類

  • 1、類介面卡模式

類介面卡簡單的 UML

類介面卡簡單的 UML

總結一下就是:介面卡「Adapter」繼承源類「Src」並且實現目標「Dst」介面,來實現 Src–>Dst 的轉換

  • 2、物件介面卡模式

物件介面卡簡單的 UML

物件介面卡簡單的 UML

總結一下就是:介面卡「Adapter」持有源類「Src」的引用,並實現目標「Dst」介面,來實現 Src–> Dst 的轉化

  • 3、介面介面卡模式

對於這種模式「資料上也沒有說有這種模式,我是在寫程式碼的過程中發現可以這樣寫」,我持保留意見,如果有什麼問題,大家完全可以說介面卡模式的分類就有以上兩種模式,可我認為這是介面卡模式的一個變種

介面介面卡簡單的 UML

介面介面卡

*總結一下就是: 介面卡實現源和目標,把源轉化成目標這麼一個過程

二、介面卡模式舉例

1、Mac 電腦連線投影儀介面卡

以開頭的例子為例子, MAC 電腦要連線投影儀器,需要一個 MINI DP 轉 VGA & HDMI 介面卡,然後才能連線上投影儀

所以這裡目標是 VGAORHDMI ,源是 MINI DP 介面卡就是上面的那根線

類適配投影儀和 MAC 電腦簡單的 UML

適配投影儀和 MAC 電腦簡單的 UML

根據 UML 擼碼

使用類介面卡模式

  • 1、定義目標介面 VgaOrHdmi
/**
 * 目標角色,對投影儀來說就要 VAG 或 HDMI
 * @auther TigerChain
 */
public interface VgaOrHdmi {
    /**輸出 VGA 或是 Hdmi 介面*/
    String getVgaOrHdmi() ;
}
  • 2、定義源類 MiniDp
/**
 * 源角色,MAC 電腦上的 MINIDP 介面
 * @auther TigerChain
 */
public class MiniDp {

    public String outPutMinkDp(){
        return "我是 mac 上的 MiniDp 輸入介面" ;
    }
}
  • 3、定義介面卡類 MidiDp2VgaOrHdmiAdapter
/**
 * 介面卡,既是 MINIDP 介面也是 VAGORHDMI 介面,這樣就可以把 MINIDP 轉成
 * VAG OR HDMI 介面
 * @auther TigerChain
 */
public class MidiDp2VgaOrHdmiAdapter extends MiniDp implements VgaOrHdmi{

    @Override
    public String getVgaOrHdmi() {
       return  convertMiniDp2VgaOrHdmi() ;
    }
    /**
     * 把 MINIDP 轉化成 VAG 或 HDMI 方法
     * @return
     */
    private String convertMiniDp2VgaOrHdmi(){
        //拿到源
        String str = outPutMinkDp();
        System.out.println(str+" 
 經過介面卡轉化 ");
        // 為這簡單起見,這裡直接修改源
        str = "輸出變成  VGA 和 HDMI 介面" ;
        return str ;
    }
}
  • 4、定義印表機類 Projector
/**
 * 這是投影儀,我就是 VGA 和 HDMI 介面的
 * @auther TigerChain
 */
public class Projector {
        // 我要的就是 VGA 或者 HDMI 介面
        public String getVgaOrHdmi(VgaOrHdmi vgaOrHdmi){
            return vgaOrHdmi.getVgaOrHdmi() ;
        }
}
  • 5、定義測試類 Test
/**
 * 測試類
 * @auther TigerChain
 */
public class Test {
    public static void main(String args[]){
        //投影儀
        Projector projector = new Projector() ;
        //介面卡
        VgaOrHdmi adapter = new MidiDp2VgaOrHdmiAdapter() ;
        // 最後得到投影儀想要的 VAG or HDMI 即可
        String str = projector.getVgaOrHdmi(adapter) ;
        System.out.println(str);
    }
}
  • 6、執行檢視結果

類介面卡實現 mini2vga

完美轉化了有木有

物件介面卡實現上述例子

物件適配投影儀和 MAC 電腦簡單的 UML

物件適配投影儀和 MAC 電腦簡單的 UML

是不是和上面的圖一樣?錯,肯定不一樣,一樣我還貼出來「我又沒病」,只有一點改變,就是介面卡不是繼承源,而是持有源的引用,程式碼修改起來非常簡單,只是修改介面卡即可「別的程式碼都是一樣的」

  • 1、修改 MidiDp2VgaOrHdmiAdapter
/**
 * 介面卡,既是 MINIDP 介面也是 VAGORHDMI 介面,這樣就可以把 MINIDP 轉成
 * VAG OR HDMI 介面
 */
public class MidiDp2VgaOrHdmiAdapter  implements VgaOrHdmi{
    
    // 修改之處 1 
    private MiniDp miniDp ;
    // 修改之處 2
    public MidiDp2VgaOrHdmiAdapter(MiniDp miniDp){
        this.miniDp = miniDp ;
    }

    @Override
    public String getVgaOrHdmi() {
       return  convertMiniDp2VgaOrHdmi() ;
    }
    /**
     * 把 MINIDP 轉化成 VAG 或 HDMI 方法
     * @return
     */
    private String convertMiniDp2VgaOrHdmi(){
        // 修改之處 3  拿到源
        String str = miniDp.outPutMinkDp();
        System.out.println(str+" 
 經過介面卡轉化 ");
        // 為這簡單起見,這裡直接修改源
        str = "輸出變成  VGA 和 HDMI 介面" ;
        return str ;
    }
}
  • 2、修改測試類,並執行檢視結果

修改測試類

物件介面卡測試類

結果和上面是一樣的

物件介面卡實現 mini2vga

介面卡模式一般情況下不是軟體設計的時候就要考慮的一種模式,它是一種隨著軟體的維護可能由於不同的開發人員,不同的產品,不同的廠家造成的功能類似而介面不相同的情況下一種解決方案「只有碰到無法改變原有設計和程式碼的情況下,才考慮適配」

2、成龍初探好萊塢

我們的功夫明星成龍初闖好萊塢的時候有一個最大的障礙就是語言問題「英文不太熟悉」,那麼最早的時候都是有翻譯者的,那麼這個翻譯員就充當了介面卡的角色「把英文翻譯成中文,或者把中文翻譯成英文」

翻譯員簡單的 UML

翻譯員簡單的 UML

根據 UML 擼程式碼

  • 1、新建 ISpeakEn 介面
/**
 * Created by tigerchain on 11/12/17.
 */
public interface ISpeakEn {
    // 說英文
    String speakEnglish(String str) ;
}
  • 2、新建 ISpeakCn 介面
/**
 * Created by tigerchain on 11/12/17.
 */
public interface ISpeakCn {
    // 說中文
    String speakCn(String str) ;
}
  • 3、翻譯的介面「介面卡」 Interpreter
/**
 * Created by tigerchain on 11/12/17.
 * 翻譯的介面
 */
public interface Interpreter {

    // 中文翻譯成英文
    void chinese2English(String str) ;
    // 英文翻譯成中文
    void english2Chinese(String str) ;
}
  • 4、具體的翻譯員小張 ZhangTranslation
/**
 * Created by tigerchain on 11/12/17.
 * 舉個例子,成龍有一個張翻譯,能把英文翻譯成中文,也能把中文翻譯成英文
 */
public class ZhangTranslation implements Iinterpreter,ISpeakCn,ISpeakEn{

    @Override
    public void chinese2English(String str) {
        translationC2E(speakCn(str));
    }

    @Override
    public void english2Chinese(String str) {
        translationE2C(str) ;
    }
    // 翻譯英文--> 中文
    private void translationE2C(String str) {
        System.out.println("小張把 "+str+" 翻譯成中文");
    }

    // 翻譯中文--> 英文
    private void translationC2E(String str){
        System.out.println("小張把 "+str+" 翻譯成英文");
    }
    @Override
    public String speakCn(String str) {
        return str ;
    }

    @Override
    public String speakEnglish(String str) {
        return str;
    }
}
  • 5、來一個老外「要對話肯定要有關建人物呀」 Foreigner
/**
 * Created by tigerchain on 11/12/17.
 * 一個老外用英文給成龍打招呼
 */
public class Foreigner implements ISpeakEn {

    @Override
    public String speakEnglish(String str) {
        String say = "Wills say:"+str ;
        System.out.println(say);
        return say ;
    }
}
  • 6、成龍上場「另一個關建人物」 JackieChan
/**
 * Created by tigerchain on 11/12/17.
 */
public class JackieChan implements ISpeakCn {

    @Override
    public String speakCn(String str) {
        String say = "成龍說:"+str ;
        System.out.println(say);
        return say ;
    }
}
  • 7、測試對話 Test
/**
 * Created by tigerchain on 11/12/17.
 * 這是一個成龍對話老外的測試類
 */
public class Test {

    public static void main(String args[]){
        // 成龍說了一句話
        JackieChan jackieChan = new JackieChan() ;
        String str = jackieChan.speakCn("你好 wills");

        // 老外說了一句
        Foreigner foreigner = new Foreigner() ;
        String str2 = foreigner.speakEnglish("Hello Jackie Chain");

        // 張翻譯翻譯
        ZhangTranslation zhangTranslation = new ZhangTranslation() ;
        zhangTranslation.chinese2English(str);
        zhangTranslation.english2Chinese(str2);
    }
}
  • 8、執行檢視結果

成龍對話結果

怎麼樣這個張翻譯「介面卡」還不錯吧,當然介面卡模式也會進化,會變種,但是萬變不離其宗「上面 Demo 就可以看作是一個變種的介面卡模式」

三、Android 原始碼中的介面卡模式

ListAdapter

沒有搞錯吧,上一節不是說了 ListAdapter 是一種策略模式嗎?沒錯它也是一種介面卡模式「從名字就可以看出來」

ListAdapter 介面卡簡單的 UML

ListAdapter 介面卡 UML

從上圖可以看出,BaseAdapter 是一個基礎介面卡,下面子類是具體各自的介面卡,這些介面卡的作用就是把資料 List,Cusor 等轉化成 ListAdapter 介面,最終讓客戶端 ListView 來呼叫「可以通俗的說就是把資料適配到 View 上面」

以 ArrayAdapter<T> 原始碼分析一下

  • 1、先看看 Adapter
public interface Adapter {
int getCount();

    Object getItem(int var1);

    long getItemId(int var1);

    boolean hasStableIds();

    View getView(int var1, View var2, ViewGroup var3);

    int getItemViewType(int var1);

    int getViewTypeCount();

    boolean isEmpty();
 }
  • 2、ListAdapter 繼承 Adapter 介面,所以擁有 Adapter 所有功能
  • 3、BaseAdapter 實現 ListAdapter 所以不僅擁有 ListAdapteer 的所有能力
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter{

    //省略苦幹程式碼
    public boolean areAllItemsEnabled() {
        return true;
    }

    public boolean isEnabled(int position) {
        return true;
    }

    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getView(position, convertView, parent);
    }

    public int getItemViewType(int position) {
        return 0;
    }

    public int getViewTypeCount() {
        return 1;
    }
    
    public boolean isEmpty() {
        return getCount() == 0;
    }    
}
  • 3、再來看看 ArrayAdapter
public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter {

 private List<T> mObjects;  
    //列出其中一個構造方法 
     public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId) {
        this(context, resource, textViewResourceId, new ArrayList<>());
    }
    
    @Override
    public int getCount() {
        return mObjects.size();
    }

    @Override
    public @Nullable T getItem(int position) {
        return mObjects.get(position);
    }

    public int getPosition(@Nullable T item) {
        return mObjects.indexOf(item);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public @NonNull View getView(int position, @Nullable View convertView,
            @NonNull ViewGroup parent) {
        return createViewFromResource(mInflater, position, convertView, parent, mResource);
    }
    //其它程式碼流省略
}

我們通過原始碼可以看到 ArrayAdapter 就是把 List 的資料來源採用一系列方法轉化成 ListAdapter 需要的幾種方法 getView getPosition 等等「這就是一個適配的過程」

ListAdapter 既是策略模式又是介面卡模式

根據選擇模式使用那種介面卡 ListAdapter 就是策略模式,但是根據每個策略所實現功能「它就是介面卡模式」

四、介面卡模式的優缺點

優點

  • 1、客戶端只關心介面卡,對客戶端來說更簡單
  • 2、現有類的複用而不需要改變,解決了現有類和目標類環境不一致的問題
  • 3、解耦「目標類和介面卡解耦」,不用改變原有的程式碼,再一個就是某天目標大變了,那麼我們再編寫一個介面卡就可以了「原來的介面卡可以扔掉了,就像某天你的 MAC 筆記本壞了,電源介面卡就可以扔了–這是一個玩笑,除非是介面卡不適用新買的 MAC」

缺點

  • 1、介面卡編寫過程需要多方考慮「可能會很複雜」
  • 2、介面卡把一個介面轉化成另一個介面,在客戶端會給人誤導,明明傳入的是 A 介面,最後成 B 了,讓人很暈

到此為止,我們就介紹完了介面卡模式,點贊是一種美德


相關文章