1. 程式人生 > >設計模式學習之介面卡模式(Adapter)

設計模式學習之介面卡模式(Adapter)

轉自:https://blog.csdn.net/zxt0601/article/details/52848004

一 概述

定義:介面卡模式將某個類的介面轉換成客戶端期望的另一個介面表示,主的目的是相容性,讓原本因介面不匹配不能一起工作的兩個類可以協同工作。其別名為包裝器(Wrapper)。

屬於結構型模式

主要分為三類:類介面卡模式、物件的介面卡模式、介面的介面卡模式。

本文定義:

需要被適配的類、介面、物件(我們有的),簡稱 src(source)
最終需要的輸出(我們想要的),簡稱 dst (destination,即Target)
介面卡稱之為 Adapter

一句話描述介面卡模式的感覺: src->Adapter->dst,即src以某種形式(三種形式分別對應三種介面卡模式)給到Adapter裡,最終轉化成了dst。

拿我們Android開發最熟悉的展示列表資料的三大控制元件:ListView,GridView,RecyclerView的Adapter來說,它們三個控制元件需要的是View(dst),而我們有的一般是datas(src),所以介面卡Adapter就是完成了資料來源datas 轉化成 ItemView的工作。
帶入src->Adapter->dst中,即datas->Adapter->View

.

使用場景:

1 系統需要使用現有的類,而這些類的介面不符合系統的需要。
2 想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
3 需要一個統一的輸出介面,而輸入端的型別不可預知。


二 類介面卡模式:

一句話描述:Adapter類,通過繼承 src類,實現 dst 類介面,完成src->dst的適配。

別的文章都用生活中充電器的例子來講解介面卡,的確,這是個極佳的舉例,本文也不能免俗:

充電器本身相當於Adapter,220V交流電相當於src,我們的目dst標是5V直流電。
我們現有的src類:

/**
 * 介紹:src類: 我們有的220V電壓
 * 作者:zhangxutong
 * 郵箱:[email protected]
 * 時間: 2016/10/18.
 */

public class Voltage220 {
    public int output220V() {
        int src = 220;
        System.out.println("我是" + src + "V");
        return src;
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

我們想要的dst介面:

/**
 * 介紹:dst介面:客戶需要的5V電壓
 * 作者:zhangxutong
 * 郵箱:[email protected]
 * 時間: 2016/10/18.
 */

public interface Voltage5 {
    int output5V();
}

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

介面卡類:

/**
 * 介紹:Adapter類:完成220V-5V的轉變
 * 通過繼承src類,實現 dst 類介面,完成src->dst的適配。
 * 作者:zhangxutong
 * 郵箱:[email protected]
 * 時間: 2016/10/18.
 */

public class VoltageAdapter extends Voltage220 implements Voltage5 {
    @Override
    public int output5V() {
        int src = output220V();
        System.out.println("介面卡工作開始適配電壓");
        int dst = src / 44;
        System.out.println("適配完成後輸出電壓:" + dst);
        return dst;
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Client類:

/**
 * 介紹:Client類:手機 .需要5V電壓
 * 作者:zhangxutong
 * 郵箱:[email protected]
 * 時間: 2016/10/18.
 */

public class Mobile {
    /**
     * 充電方法
     *
     * @param voltage5
     */
    public void charging(Voltage5 voltage5) {
        if (voltage5.output5V() == 5) {
            System.out.println("電壓剛剛好5V,開始充電");
        } else if (voltage5.output5V() > 5) {
            System.out.println("電壓超過5V,都閃開 我要變成note7了");
        }
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

測試程式碼:

        System.out.println("===============類介面卡==============");
        Mobile mobile = new Mobile();
        mobile.charging(new VoltageAdapter());
  
  • 1
  • 2
  • 3

輸出:

===============類介面卡==============
我是220V
介面卡工作開始適配電壓
適配完成後輸出電壓:5
電壓剛剛好5V,開始充電
  
  • 1
  • 2
  • 3
  • 4
  • 5

類圖如下:
這裡寫圖片描述

小結:

Java這種單繼承的機制,所有需要繼承的我個人都不太喜歡。
所以類介面卡需要繼承src類這一點算是一個缺點,
因為這要求dst必須是介面,有一定侷限性;
且src類的方法在Adapter中都會暴露出來,也增加了使用的成本。

但同樣由於其繼承了src類,所以它可以根據需求重寫src類的方法,使得Adapter的靈活性增強了。

三 物件介面卡模式(常用):

基本思路和類的介面卡模式相同,只是將Adapter類作修改,這次不繼承src類,而是持有src類的例項,以解決相容性的問題。
即:持有 src類,實現 dst 類介面,完成src->dst的適配。
(根據“合成複用原則”,在系統中儘量使用關聯關係來替代繼承關係,因此大部分結構型模式都是物件結構型模式。)
Adapter類如下:

/**
 * 介紹:物件介面卡模式:
 * 持有 src類,實現 dst 類介面,完成src->dst的適配。 。以達到解決**相容性**的問題。
 * 作者:zhangxutong
 * 郵箱:[email protected]
 * 時間: 2016/10/18.
 */

public class VoltageAdapter2 implements Voltage5 {
    private Voltage220 mVoltage220;

    public VoltageAdapter2(Voltage220 voltage220) {
        mVoltage220 = voltage220;
    }

    @Override
    public int output5V() {
        int dst = 0;
        if (null != mVoltage220) {
            int src = mVoltage220.output220V();
            System.out.println("物件介面卡工作,開始適配電壓");
            dst = src / 44;
            System.out.println("適配完成後輸出電壓:" + dst);
        }
        return dst;
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

測試程式碼:

        System.out.println("\n===============物件介面卡==============");
        VoltageAdapter2 voltageAdapter2 = new VoltageAdapter2(new Voltage220());
        Mobile mobile2 = new Mobile();
        mobile2.charging(voltageAdapter2);
  
  • 1
  • 2
  • 3
  • 4

輸出:

===============物件介面卡==============
我是220V
物件介面卡工作,開始適配電壓
適配完成後輸出電壓:5
電壓剛剛好5V,開始充電
  
  • 1
  • 2
  • 3
  • 4
  • 5

類圖:
這裡寫圖片描述

小結:

物件介面卡和類介面卡其實算是同一種思想,只不過實現方式不同。
根據合成複用原則,組合大於繼承,
所以它解決了類介面卡必須繼承src的侷限性問題,也不再強求dst必須是介面。
同樣的它使用成本更低,更靈活。

(和裝飾者模式初學時可能會弄混,這裡要搞清,裝飾者是對src的裝飾,使用者毫無察覺到src已經被裝飾了(使用者用法不變)。 這裡物件適配以後,使用者的用法還是變的。
即,裝飾者用法: setSrc->setSrc,物件介面卡用法:setSrc->setAdapter.)

四 介面介面卡模式

也有文獻稱之為認介面卡模式(Default Adapter Pattern)或預設介面卡模式。
定義:
當不需要全部實現介面提供的方法時,可先設計一個抽象類實現介面,併為該介面中每個方法提供一個預設實現(空方法),那麼該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求,它適用於一個介面不想使用其所有的方法的情況。

我們直接進入大家最喜愛的原始碼撐腰環節:

原始碼撐腰環節:

Android中的屬性動畫ValueAnimator類可以通過addListener(AnimatorListener listener)方法新增監聽器,
那麼常規寫法如下:

        ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator.start();
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

有時候我們不想實現Animator.AnimatorListener介面的全部方法,我們只想監聽onAnimationStart,我們會如下寫:

        ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                //xxxx具體實現
            }
        });
        valueAnimator.start();
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

顯然,這個AnimatorListenerAdapter類,就是一個介面介面卡。
檢視該Adapter類原始碼:

public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
        Animator.AnimatorPauseListener {
    @Override
    public void onAnimationCancel(Animator animation) {
    }

    @Override
    public void onAnimationEnd(Animator animation) {
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
    }

    @Override
    public void onAnimationStart(Animator animation) {
    }

    @Override
    public void onAnimationPause(Animator animation) {
    }

    @Override
    public void onAnimationResume(Animator animation) {
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

可見,它空實現了Animator.AnimatorListener類(src)的所有方法.
對應的src類:

    public static interface AnimatorListener {
        void onAnimationStart(Animator animation);

        void onAnimationEnd(Animator animation);

        void onAnimationCancel(Animator animation);

        void onAnimationRepeat(Animator animation);
    }
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

類圖:

這裡寫圖片描述
我們程式裡的匿名內部類就是Listener1 2 這種具體實現類。

        new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                //xxxx具體實現
            }
        }
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

介面介面卡模式很好理解,令我們的程式更加簡潔明瞭。

五 總結

我個人理解,三種命名方式,是根據 src是以怎樣的形式給到Adapter(在Adapter裡的形式)來命名的。
類介面卡,以類給到,在Adapter裡,就是將src當做類,繼承
物件介面卡,以物件給到,在Adapter裡,將src作為一個物件,持有
介面介面卡,以介面給到,在Adapter裡,將src作為一個介面,實現

Adapter模式最大的作用還是將原本不相容的介面融合在一起工作。
但是在實際開發中,實現起來不拘泥於本文介紹的三種經典形式
例如Android中ListView、GridView的介面卡Adapter,就不是以上三種經典形式之一,
我個人理解其屬於物件介面卡模式,一般日常使用中,我們都是在Adapter裡持有datas,然後通過getView()/onCreateViewHolder()方法向ListView/RecyclerView提供View/ViewHolder。

Client是Lv Gv Rv ,它們是顯示View的類。
所以dst(Target)是View。
一般來說我們有的src是資料datas,
即,我們希望:datas(src)->Adapter->View(dst)->Rv(Client)。

文中原始碼傳送門:
https://github.com/mcxtzhang/Demos/tree/master/libadapterpattern