JAVA / Android 設(shè)計(jì)模式之適配器(Adapter)模式

簡介

定義

適配器模式,即定義一個(gè)包裝類芯砸,用于包裝不兼容接口的對象

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

主要作用

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

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

解決的問題

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


模式原理

類的適配器模式

類的適配器模式是把適配的類的API轉(zhuǎn)換成為目標(biāo)類的API。

UML 類圖 和 組成
這里寫圖片描述

在上圖中可以看出:

  • 沖突:Target期待調(diào)用Request方法包帚,而Adaptee并沒有(這就是所謂的不兼容了)渔期。
  • 解決方案:為使Target能夠使用Adaptee類里的SpecificRequest方法,故提供一個(gè)中間環(huán)節(jié)Adapter類(繼承Adaptee & 實(shí)現(xiàn)Target接口),把Adaptee的API與Target的API銜接起來(適配)疯趟。

Adapter與Adaptee是繼承關(guān)系拘哨,這決定了這個(gè)適配器模式是類的

對象的適配器模式

與類的適配器模式相同,對象的適配器模式也是把適配的類的API轉(zhuǎn)換成為目標(biāo)類的API信峻。

  • 與類的適配器模式不同的是宅静,對象的適配器模式不是使用繼承關(guān)系連接到Adaptee類,而是使用委派關(guān)系連接到Adaptee類站欺。
UML 類圖 和 組成
這里寫圖片描述

在上圖中可以看出:

  • 沖突:Target期待調(diào)用Request方法姨夹,而Adaptee并沒有(這就是所謂的不兼容了)。
  • 解決方案:為使Target能夠使用Adaptee類里的SpecificRequest方法矾策,故提供一個(gè)中間環(huán)節(jié)Adapter類(包裝了一個(gè)Adaptee的實(shí)例)磷账,把Adaptee的API與Target的API銜接起來(適配)。

Adapter與Adaptee是委派關(guān)系贾虽,這決定了適配器模式是對象的逃糟。


適配器模式的使用

類適配器模式的使用

步驟1:創(chuàng)建Target接口

public interface Target {

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

步驟2: 創(chuàng)建源類(Adaptee)

public class Adaptee {

    public void SpecificRequest(){
    }
}

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

//適配器Adapter繼承自Adaptee,同時(shí)又實(shí)現(xiàn)了目標(biāo)(Target)接口蓬豁。
public class Adapter extends Adaptee implements Target {

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

}

步驟4: 定義具體使用目標(biāo)類地粪,并通過Adapter類調(diào)用所需要的方法從而實(shí)現(xiàn)目標(biāo)

public class AdapterPattern {

    public static void main(String[] args){

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

    }
}

對象適配器模式的使用

步驟1:創(chuàng)建Target接口

public interface Target {

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

步驟2: 創(chuàng)建源類(Adaptee)

public class Adaptee {

    public void SpecificRequest(){
    }
}

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

class Adapter implements Target{  
    // 直接關(guān)聯(lián)被適配類  
    private Adaptee adaptee;  

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

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

步驟4: 定義具體使用目標(biāo)類,并通過Adapter類調(diào)用所需要的方法從而實(shí)現(xiàn)目標(biāo)

public class AdapterPattern {
    public static void main(String[] args){
        //需要先創(chuàng)建一個(gè)被適配類的對象作為參數(shù)  
        Target mAdapter = new Adapter(new Adaptee())蟆技;
        mAdapter.Request();

    }
}

例子

  • 背景:定春買了一個(gè)進(jìn)口的電視機(jī)
  • 沖突:進(jìn)口電視機(jī)要求電壓(110V)與國內(nèi)插頭標(biāo)準(zhǔn)輸出電壓(220V)不兼容
  • 解決方案:設(shè)置一個(gè)適配器將插頭輸出的220V轉(zhuǎn)變成110V
實(shí)現(xiàn)步驟

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

public interface Target {

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

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

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

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

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

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

步驟4: 定義具體使用目標(biāo)類质礼,并通過Adapter類調(diào)用所需要的方法從而實(shí)現(xiàn)目標(biāo)(不需要通過原有插頭)

//進(jìn)口機(jī)器類
class ImportedMachine {

    @Override
    public void Work() {
        System.out.println("進(jìn)口機(jī)器正常運(yùn)行");
    }
}


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

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

        //用戶拿著進(jìn)口機(jī)器插上適配器(調(diào)用Convert_110v()方法)
        //再將適配器插上原有插頭(Convert_110v()方法內(nèi)部調(diào)用Output_220v()方法輸出220V)
        //適配器只是個(gè)外殼旺聚,對外提供110V,但本質(zhì)還是220V進(jìn)行供電
        mAdapter220V.Convert_110v();
        mImportedMachine.Work();
    }
}
  • 對象適配器模式的同理眶蕉,只是在適配類實(shí)現(xiàn)時(shí)將“繼承”改成“在內(nèi)部委派Adaptee類”而已

Android 中的適配器模式

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

// 代碼省略
 ListView myListView = (ListView)findViewById(listview_id);
 // 設(shè)置適配器
 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;
        }
 
        // 解析砰粹、設(shè)置、緩存convertView以及相關(guān)內(nèi)容
        @Override
        public View getView(int position, View convertView, ViewGroup parent) { 
            ViewHolder holder = null;
            // Item View的復(fù)用
            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;
        }
 
    }

因?yàn)長istView需要能夠顯示各式各樣的視圖造挽,每個(gè)人需要的顯示效果各不相同碱璃,顯示的數(shù)據(jù)類型、數(shù)量等也千變?nèi)f化刽宪。

為了能夠進(jìn)行統(tǒng)一厘贼,將ListView需要的接口抽象到Adapter對象中界酒,這樣只要用戶實(shí)現(xiàn)了Adapter的接口圣拄,它使用的就是對象適配器模式,我們只需要將數(shù)據(jù)源委托給Adapter毁欣,Adapter按照目標(biāo)接口對數(shù)據(jù)源進(jìn)行處理庇谆,并且提供給ListView岳掐,這樣ListView就可以按照用戶設(shè)定的顯示效果、數(shù)量饭耳、數(shù)據(jù)來顯示特定的Item View串述。

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

  • 將BaseAdaper看做是Target寞肖,它跟Adapter是一種繼承(實(shí)現(xiàn))關(guān)系纲酗,
  • 將List<String>類型的mDate看作是Adaptee,它跟Adapter是一種委托關(guān)系新蟆。

因?yàn)閿?shù)據(jù)源的千變?nèi)f化導(dǎo)致對ListView的適配困難觅赊,現(xiàn)在通過Adapter對數(shù)據(jù)源的統(tǒng)一適配處理,這樣我們的ListView就可以按照固定的規(guī)范訪問Adapter就可以了琼稻,因?yàn)锳dapter是統(tǒng)一的吮螺,都是實(shí)現(xiàn)自BaseAdapter。


優(yōu)缺點(diǎn)

適配器模式

優(yōu)點(diǎn)
  • 更好的復(fù)用性
    系統(tǒng)需要使用現(xiàn)有的類帕翻,而此類的接口不符合系統(tǒng)的需要鸠补。那么通過適配器模式就可以讓這些功能得到更好的復(fù)用。
  • 透明嘀掸、簡單
    客戶端可以調(diào)用同一接口紫岩,因而對客戶端來說是透明的。這樣做更簡單 & 更直接
  • 更好的擴(kuò)展性
    在實(shí)現(xiàn)適配器功能的時(shí)候睬塌,可以調(diào)用自己開發(fā)的功能被因,從而自然地?cái)U(kuò)展系統(tǒng)的功能。
  • 解耦性
    將目標(biāo)類和適配者類解耦衫仑,通過引入一個(gè)適配器類重用現(xiàn)有的適配者類梨与,而無需修改原有代碼
  • 符合開放-關(guān)閉原則
    同一個(gè)適配器可以把適配者類和它的子類都適配到目標(biāo)接口;可以為不同的目標(biāo)接口實(shí)現(xiàn)不同的適配器文狱,而不需要修改待適配類
缺點(diǎn)
  • 過多的使用適配器粥鞋,會(huì)讓系統(tǒng)非常零亂,不易整體進(jìn)行把握

類的適配器模式

優(yōu)點(diǎn)
  • 使用方便瞄崇,代碼簡化
    僅僅引入一個(gè)對象呻粹,并不需要額外的字段來引用Adaptee實(shí)例
缺點(diǎn)
  • 高耦合,靈活性低
    使用對象繼承的方式苏研,是靜態(tài)的定義方式

對象的適配器模式

優(yōu)點(diǎn)
  • 靈活性高等浊、低耦合
    采用 “對象組合”的方式,是動(dòng)態(tài)組合方式
缺點(diǎn)
  • 使用復(fù)雜
    需要引入對象實(shí)例

總結(jié)

適配器的使用場景

  • 系統(tǒng)需要復(fù)用現(xiàn)有類摹蘑,而該類的接口不符合系統(tǒng)的需求筹燕,可以使用適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作
  • 多個(gè)組件功能類似,但接口不統(tǒng)一且可能會(huì)經(jīng)常切換時(shí),可使用適配器模式撒踪,使得客戶端可以以統(tǒng)一的接口使用它們

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

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

  • 類適配器使用對象繼承的方式过咬,是靜態(tài)的定義方式;而對象適配器使用對象組合的方式制妄,是動(dòng)態(tài)組合的方式掸绞。

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

  • 對于類適配器,由于適配器直接繼承了Adaptee耕捞,使得適配器不能和Adaptee的子類一起工作衔掸,因?yàn)槔^承是靜態(tài)的關(guān)系,當(dāng)適配器繼承了Adaptee后俺抽,就不可能再去處理 Adaptee的子類了具篇;
  • 對于對象適配器,一個(gè)適配器可以把多種不同的源適配到同一個(gè)目標(biāo)凌埂。換言之驱显,同一個(gè)適配器可以把源類和它的子類都適配到目標(biāo)接口。因?yàn)閷ο筮m配器采用的是對象組合的關(guān)系瞳抓,只要對象類型正確埃疫,是不是子類都無所謂。

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

  • 對于類適配器孩哑,適配器可以重定義Adaptee的部分行為栓霜,相當(dāng)于子類覆蓋父類的部分實(shí)現(xiàn)方法。
  • 對于對象適配器横蜒,要重定義Adaptee的行為比較困難胳蛮,這種情況下,需要定義Adaptee的子類來實(shí)現(xiàn)重定義丛晌,然后讓適配器組合子類仅炊。雖然重定義Adaptee的行為比較困難,但是想要增加一些新的行為則方便的很澎蛛,而且新增加的行為可同時(shí)適用于所有的源抚垄。

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

  • 對于類適配器,僅僅引入了一個(gè)對象谋逻,并不需要額外的引用來間接得到Adaptee呆馁。
  • 對于對象適配器,需要額外的引用來間接得到Adaptee毁兆。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浙滤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子气堕,更是在濱河造成了極大的恐慌纺腊,老刑警劉巖畔咧,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異摹菠,居然都是意外死亡盒卸,警方通過查閱死者的電腦和手機(jī)骗爆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門次氨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摘投,你說我怎么就攤上這事煮寡。” “怎么了犀呼?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵幸撕,是天一觀的道長。 經(jīng)常有香客問我外臂,道長坐儿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任宋光,我火速辦了婚禮貌矿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罪佳。我一直安慰自己逛漫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布赘艳。 她就那樣靜靜地躺著酌毡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蕾管。 梳的紋絲不亂的頭發(fā)上枷踏,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音掰曾,去河邊找鬼呕寝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛婴梧,可吹牛的內(nèi)容都是我干的下梢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼塞蹭,長吁一口氣:“原來是場噩夢啊……” “哼孽江!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起番电,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤岗屏,失蹤者是張志新(化名)和其女友劉穎辆琅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體这刷,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婉烟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暇屋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片似袁。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咐刨,靈堂內(nèi)的尸體忽然破棺而出昙衅,到底是詐尸還是另有隱情,我是刑警寧澤定鸟,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布而涉,位于F島的核電站,受9級特大地震影響联予,放射性物質(zhì)發(fā)生泄漏啼县。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一沸久、第九天 我趴在偏房一處隱蔽的房頂上張望季眷。 院中可真熱鬧,春花似錦麦向、人聲如沸瘟裸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽话告。三九已至,卻和暖如春卵慰,著一層夾襖步出監(jiān)牢的瞬間沙郭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工裳朋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留病线,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓鲤嫡,卻偏偏與公主長得像送挑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子暖眼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容