問題:在應(yīng)用程序中,我們可能會使用一個在體系結(jié)構(gòu)上可靠穩(wěn)定的工作代碼庫。不過,我們常常會添加新的功能,這些功能要求采用不同的方式使用現(xiàn)有的對象,而不是采用原先設(shè)計的方式。此時,障礙可能只是新功能需要一個不同的名字。在較為復(fù)雜的場景中,障礙可能是新功能需要與原始對象稍有不同的行為。就像一開始我們開發(fā)某個網(wǎng)站,是基于PC端的,后來要兼容移動端,我們發(fā)現(xiàn)PC端和移動端也就只有一些方面不同,其他都相同。有沒有一種能夠適配的方式呢?
概念:適配器模式就是某個對象的接口適配為另一個對象所期望的接口。這相當(dāng)于我們現(xiàn)實生活中的電源,我們僅僅有USB插頭是不能充電的,而且插座有三孔和兩孔的,我們要充電,就必須要使用一個轉(zhuǎn)接插頭,來兼容USB線的插孔。
何時使用:希望在原有對象和現(xiàn)有對象之間提供一個中間轉(zhuǎn)換的時候。
適配器模式
類的適配器模式把適配的類的API轉(zhuǎn)換成為目標(biāo)類的API。

在上圖中可以看出,Adaptee類并沒有example2()方法,而客戶端則期待這個方法。為使客戶端能夠使用Adaptee類,提供一個中間環(huán)節(jié),即類Adapter,把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是繼承關(guān)系,這決定了這個適配器模式是類的。當(dāng)然,在這里我們可以看到,Adapter類既要繼承ITarget接口和Adaptee類,然而,不管是PHP還是java,都沒有雙繼承的概念,所以,這里采用對接一個接口和繼承一個類的方式來模擬雙繼承,就如:class Adapter extends Adaptee implements ITarget{}。下面我們就要金幣的問題來大致演示一下,用人民幣(RMB)方法來代表上圖的Adaptee,用美元(IDollar)方法來代表上圖的ITarget,用以從人民幣到美元的需求轉(zhuǎn)換。
大致代碼如下:
interface?IDollar{
/*
*這是源類Adaptee沒有的方法
*/
public?function?example2();
}
上面給出的是目標(biāo)角色的源代碼,這個角色是以接口的形式實現(xiàn)的。可以看出,這個接口聲明了一個方法:example2()。而源角色Adaptee是一個具體類,它有一個example1()方法,但是沒有example2()方法。
class?RMB{
? ? public?$rate=1;
? ? private?$danjia1;
? ? private?$danjia2;
? ? private?$total;
? ? public?function?receive($price1,$price2){
? ? ? ? ? ? ? $this->?danjia1?=?$price1;
? ? ? ? ? ? ? ?$this->danjia2?=?$price2;
? ? ? ? ? ? ? ?$this->total();
}
protect?function?total(){
? ? ? ? ?//具體實現(xiàn)過程,最終返回總錢結(jié)果
? ? ? ? ?$total?=?($this->danjia1+$this->danjia2)*$rate;
? ? ? ? ?return?$total;
? ? }
}
適配器角色Adapter擴(kuò)展了Adaptee,同時又實現(xiàn)了目標(biāo)(Target)接口。由于Adaptee沒有提供example2()方法,而目標(biāo)接口又要求這個方法,因此適配器角色Adapter實現(xiàn)了這個方法。
class?DollarAdapter?extends?RMB?implements?IDollar{
/*
*適配器補(bǔ)上沒有的方法
*/
public?function?example2(){
? ? ? ?$this->rate?=?0.1488;
? ? ? ? //具體實現(xiàn),最終返回結(jié)果
? ? ? ?}
}
//下面來看一下調(diào)用
class?Client{
? ? private?$RMBNow;
? ? private?$DollarNow;
? ? public?function?__construct(){
? ? ? ? ? ?$this->RMBNow?=?new?RMB();
? ? ? ? ? ?$this->DollarNow?=?new?DollarAdapter();
? ? ? ? ? ?echo?'人民幣收入為:'?.?$this->RMB_total($this->RMBNow?);
? ? ? ? ? ?echo?'相當(dāng)于的美元數(shù)為::'?.?$this->Dollar_total($this->DollarNow?);
}
private?function?RMB_total(RMB?$res){
? ? ? ? ? //上面的括號里使用類型提示,可以保證與接口一致,下面的方法也是
? ? ? ? ? return?$res->receive(30,70);
}
private?function?RMB_total(IDollar?$res){
? ? ? ? ? ? //由于最后匯率變了,所以最終調(diào)用的total方法的錢也會變成匯率的
? ? ? ? ? ?return?$res->receive(30,70);
? ? ? }
}
對象適配器模式

從上圖可以看出,Adaptee類并沒有example2()方法,而客戶端則期待這個方法。為使客戶端能夠使用Adaptee類,需要提供一個包裝類Adapter。這個包裝類包裝了一個Adaptee的實例,從而此包裝類能夠把Adaptee的API與ITarget類的API銜接起來。Adapter與Adaptee是委派關(guān)系,這決定了適配器模式是對象的。
簡單代碼如下:
interface?ITarget{
? ? ? ? public?function?example1();
? ? ? ? public?function?example2();
}
class?Adaptee{
? ? ? ? public?function?example1(){
? ? ? ? ? //具體實現(xiàn)
? ? }
}
public?function?Adapter?implements?ITarget{
? ? ? ? private?$adaptee;
? ? ? ? public?function?Adaptee(Adaptee?$apate){
? ? ? ? ?$this->adaptee?=?$apate;
? ? ?}
/*
*源類Adaptee有方法example1,因此,適配器直接委派即可
*/
public?function?example1(){
? ? ? ? $this->adaptee->example1();
? ?}
/*
*源類Adaptee沒有example2方法,因此由適配器補(bǔ)充
*/
public?function?example2(){
? ? ? ?//具體實現(xiàn)
? ? ?}
}
類適配器和對象適配器的比較:
類適配器比較簡單,原因在于適配器會從被適配者那里繼承功能,需要重新編寫的代碼比較少。由于給定了一個將由適配器(Adapter)繼承的具體適配者(Adaptee),這種綁定很緊密,所以使用類適配模式創(chuàng)建時,必須非常清楚將在哪里發(fā)生適配。
對于類適配器,由于適配器直接繼承了Adaptee,使得適配器不能和Adaptee的子類一起工作,因為繼承是靜態(tài)的關(guān)系,當(dāng)適配器繼承了Adaptee后,就不可能再去處理? Adaptee的子類了。而對于對象適配器,一個適配器可以把多種不同的源適配到同一個目標(biāo)。換言之,同一個適配器可以把源類和它的子類都適配到目標(biāo)接口。因為對象適配器采用的是對象組合的關(guān)系,只要對象類型正確,是不是子類都無所謂。
對于類適配器,僅僅引入了一個對象,并不需要額外的引用來間接得到Adaptee。而對于對象適配器,需要額外的引用來間接得到Adaptee。
適配器優(yōu)點:
1.更好的復(fù)用性:系統(tǒng)需要使用現(xiàn)有的類,而此類的接口不符合系統(tǒng)的需要。那么通過適配器模式就可以讓這些功能得到更好的復(fù)用。
2.更好的擴(kuò)展性:在實現(xiàn)適配器功能的時候,可以調(diào)用自己開發(fā)的功能,從而自然地擴(kuò)展系統(tǒng)的功能。
適配器缺點:
過多的使用適配器,會讓系統(tǒng)非常零亂,不易整體進(jìn)行把握。比如,明明看到調(diào)用的是A接口,其實內(nèi)部被適配成了B接口的實現(xiàn),一個系統(tǒng)如果太多出現(xiàn)這種情況,無異于一場災(zāi)難。