簡介
定義
適配器模式,即定義一個(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毁兆。