JAVA / Android 設計模式之適配器(Adapter)模式

簡介

定義

適配器模式,即定義一個包裝類,用於包裝不兼容接口的對象

  • 包裝類 = 適配器Adapter;
  • 被包裝對象 = 適配者Adaptee = 被適配的類

主要作用

把一個類的接口變換成客戶端所期待的另一種接口,從而使原本接口不匹配而無法一起工作的兩個類能夠在一起工作。

  • 適配器模式的形式分爲:類的適配器模式和對象的適配器模式

解決的問題

原本由於接口不兼容而不能一起工作的那些類可以在一起工作。


模式原理

類的適配器模式

類的適配器模式是把適配的類的API轉換成爲目標類的API。

UML 類圖 和 組成

這裏寫圖片描述

在上圖中可以看出:

  • 衝突:Target期待調用Request方法,而Adaptee並沒有(這就是所謂的不兼容了)。
  • 解決方案:爲使Target能夠使用Adaptee類裏的SpecificRequest方法,故提供一箇中間環節Adapter類(繼承Adaptee & 實現Target接口),把Adaptee的API與Target的API銜接起來(適配)。

Adapter與Adaptee是繼承關係,這決定了這個適配器模式是類的

對象的適配器模式

與類的適配器模式相同,對象的適配器模式也是把適配的類的API轉換成爲目標類的API。

  • 與類的適配器模式不同的是,對象的適配器模式不是使用繼承關係連接到Adaptee類,而是使用委派關係連接到Adaptee類。
UML 類圖 和 組成

這裏寫圖片描述

在上圖中可以看出:

  • 衝突:Target期待調用Request方法,而Adaptee並沒有(這就是所謂的不兼容了)。
  • 解決方案:爲使Target能夠使用Adaptee類裏的SpecificRequest方法,故提供一箇中間環節Adapter類(包裝了一個Adaptee的實例),把Adaptee的API與Target的API銜接起來(適配)。

Adapter與Adaptee是委派關係,這決定了適配器模式是對象的。


適配器模式的使用

類適配器模式的使用

步驟1:創建Target接口

public interface Target {

    //這是源類Adapteee沒有的方法
    public void Request(); 
}

步驟2: 創建源類(Adaptee)

public class Adaptee {

    public void SpecificRequest(){
    }
}

步驟3: 創建適配器類(Adapter)

//適配器Adapter繼承自Adaptee,同時又實現了目標(Target)接口。
public class Adapter extends Adaptee implements Target {

    //目標接口要求調用Request()這個方法名,但源類Adaptee沒有方法Request()
    //因此適配器補充上這個方法名
    //但實際上Request()只是調用源類Adaptee的SpecificRequest()方法的內容
    //所以適配器只是將SpecificRequest()方法作了一層封裝,封裝成Target可以調用的Request()而已
    @Override
    public void Request() {
        this.SpecificRequest();
    }

}

步驟4: 定義具體使用目標類,並通過Adapter類調用所需要的方法從而實現目標

public class AdapterPattern {

    public static void main(String[] args){

        Target mAdapter = new Adapter();
        mAdapter.Request();

    }
}

對象適配器模式的使用

步驟1:創建Target接口

public interface Target {

    //這是源類Adaptee沒有的方法
    public void Request(); 
}

步驟2: 創建源類(Adaptee)

public class Adaptee {

    public void SpecificRequest(){
    }
}

步驟3: 創建適配器類(Adapter)(不適用繼承而是委派)

class Adapter implements Target{  
    // 直接關聯被適配類  
    private Adaptee adaptee;  

    // 可以通過構造函數傳入具體需要適配的被適配類對象  
    public Adapter (Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  

    @Override
    public void Request() {  
        // 這裏是使用委託的方式完成特殊功能  
        this.adaptee.SpecificRequest();  
    }  
}  

步驟4: 定義具體使用目標類,並通過Adapter類調用所需要的方法從而實現目標

public class AdapterPattern {
    public static void main(String[] args){
        //需要先創建一個被適配類的對象作爲參數  
        Target mAdapter = new Adapter(new Adaptee());
        mAdapter.Request();

    }
}

例子

  • 背景:定春買了一個進口的電視機
  • 衝突:進口電視機要求電壓(110V)與國內插頭標準輸出電壓(220V)不兼容
  • 解決方案:設置一個適配器將插頭輸出的220V轉變成110V
實現步驟

步驟1: 創建Target接口(期待得到的插頭):能輸出110V(將220V轉換成110V)

public interface Target {

    //將220V轉換輸出110V(原有插頭(Adaptee)沒有的)
    public void Convert_110v();
}

步驟2: 創建源類(原有的插頭)

class PowerPort220V{
//原有插頭只能輸出220V
    public void Output_220v(){
    }
}

步驟3: 創建適配器類(Adapter)

class Adapter220V extends PowerPort220V implements Target{
   //期待的插頭要求調用Convert_110v(),但原有插頭沒有
    //因此適配器補充上這個方法名
    //但實際上Convert_110v()只是調用原有插頭的Output_220v()方法的內容
    //所以適配器只是將Output_220v()作了一層封裝,封裝成Target可以調用的Convert_110v()而已

    @Override
    public void Convert_110v(){
      this.Output_220v;
    }
}

步驟4: 定義具體使用目標類,並通過Adapter類調用所需要的方法從而實現目標(不需要通過原有插頭)

//進口機器類
class ImportedMachine {

    @Override
    public void Work() {
        System.out.println("進口機器正常運行");
    }
}


//通過Adapter類從而調用所需要的方法
public class AdapterPattern {
    public static void main(String[] args){

        Target mAdapter220V = new Adapter220V();
        ImportedMachine mImportedMachine = new ImportedMachine();

        //用戶拿着進口機器插上適配器(調用Convert_110v()方法)
        //再將適配器插上原有插頭(Convert_110v()方法內部調用Output_220v()方法輸出220V)
        //適配器只是個外殼,對外提供110V,但本質還是220V進行供電
        mAdapter220V.Convert_110v();
        mImportedMachine.Work();
    }
}
  • 對象適配器模式的同理,只是在適配類實現時將“繼承”改成“在內部委派Adaptee類”而已

Android 中的適配器模式

  • ListView / RecyclerView的Adapter 是我們最常見的類型之一

// 代碼省略
 ListView myListView = (ListView)findViewById(listview_id);
 // 設置適配器
 myListView.setAdapter(new MyAdapter(context, myDatas));

// 適配器
public class MyAdapter extends BaseAdapter{

        private LayoutInflater mInflater;
        List<String> mDatas ; 

        public MyAdapter(Context context, List<String> datas){
            this.mInflater = LayoutInflater.from(context);
            mDatas = datas ;
        }
        @Override
        public int getCount() {
            return mDatas.size();
        }

        @Override
        public String getItem(int pos) {
            return mDatas.get(pos);
        }

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

        // 解析、設置、緩存convertView以及相關內容
        @Override
        public View getView(int position, View convertView, ViewGroup parent) { 
            ViewHolder holder = null;
            // Item View的複用
            if (convertView == null) {
                holder = new ViewHolder();  
                convertView = mInflater.inflate(R.layout.my_listview_item, null);
                // 獲取title
                holder.title = (TextView)convertView.findViewById(R.id.title);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.title.setText(mDatas.get(position));
            return convertView;
        }

    }

因爲ListView需要能夠顯示各式各樣的視圖,每個人需要的顯示效果各不相同,顯示的數據類型、數量等也千變萬化。

爲了能夠進行統一,將ListView需要的接口抽象到Adapter對象中,這樣只要用戶實現了Adapter的接口,它使用的就是對象適配器模式,我們只需要將數據源委託給Adapter,Adapter按照目標接口對數據源進行處理,並且提供給ListView,這樣ListView就可以按照用戶設定的顯示效果、數量、數據來顯示特定的Item View。

在上面的例子中,我們可以

  • 將BaseAdaper看做是Target,它跟Adapter是一種繼承(實現)關係,
  • 將List類型的mDate看作是Adaptee,它跟Adapter是一種委託關係。

因爲數據源的千變萬化導致對ListView的適配困難,現在通過Adapter對數據源的統一適配處理,這樣我們的ListView就可以按照固定的規範訪問Adapter就可以了,因爲Adapter是統一的,都是實現自BaseAdapter。


優缺點

適配器模式

優點
  • 更好的複用性
    系統需要使用現有的類,而此類的接口不符合系統的需要。那麼通過適配器模式就可以讓這些功能得到更好的複用。
  • 透明、簡單
    客戶端可以調用同一接口,因而對客戶端來說是透明的。這樣做更簡單 & 更直接
  • 更好的擴展性
    在實現適配器功能的時候,可以調用自己開發的功能,從而自然地擴展系統的功能。
  • 解耦性
    將目標類和適配者類解耦,通過引入一個適配器類重用現有的適配者類,而無需修改原有代碼
  • 符合開放-關閉原則
    同一個適配器可以把適配者類和它的子類都適配到目標接口;可以爲不同的目標接口實現不同的適配器,而不需要修改待適配類
缺點
  • 過多的使用適配器,會讓系統非常零亂,不易整體進行把握

類的適配器模式

優點
  • 使用方便,代碼簡化
    僅僅引入一個對象,並不需要額外的字段來引用Adaptee實例
缺點
  • 高耦合,靈活性低
    使用對象繼承的方式,是靜態的定義方式

對象的適配器模式

優點
  • 靈活性高、低耦合
    採用 “對象組合”的方式,是動態組合方式
缺點
  • 使用複雜
    需要引入對象實例

總結

適配器的使用場景

  • 系統需要複用現有類,而該類的接口不符合系統的需求,可以使用適配器模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作
  • 多個組件功能類似,但接口不統一且可能會經常切換時,可使用適配器模式,使得客戶端可以以統一的接口使用它們

類和對象適配器模式的使用場景

1: 靈活使用時:選擇對象的適配器模式

  • 類適配器使用對象繼承的方式,是靜態的定義方式;而對象適配器使用對象組合的方式,是動態組合的方式。

2: 需要同時配源類和其子類:選擇對象的適配器

  • 對於類適配器,由於適配器直接繼承了Adaptee,使得適配器不能和Adaptee的子類一起工作,因爲繼承是靜態的關係,當適配器繼承了Adaptee後,就不可能再去處理 Adaptee的子類了;
  • 對於對象適配器,一個適配器可以把多種不同的源適配到同一個目標。換言之,同一個適配器可以把源類和它的子類都適配到目標接口。因爲對象適配器採用的是對象組合的關係,只要對象類型正確,是不是子類都無所謂。

3: 需要重新定義Adaptee的部分行爲:選擇類適配器

  • 對於類適配器,適配器可以重定義Adaptee的部分行爲,相當於子類覆蓋父類的部分實現方法。
  • 對於對象適配器,要重定義Adaptee的行爲比較困難,這種情況下,需要定義Adaptee的子類來實現重定義,然後讓適配器組合子類。雖然重定義Adaptee的行爲比較困難,但是想要增加一些新的行爲則方便的很,而且新增加的行爲可同時適用於所有的源。

4: 僅僅希望使用方便時:選擇類適配器

  • 對於類適配器,僅僅引入了一個對象,並不需要額外的引用來間接得到Adaptee。
  • 對於對象適配器,需要額外的引用來間接得到Adaptee。
發佈了82 篇原創文章 · 獲贊 215 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章