本文實(shí)例講述了PHP設(shè)計(jì)模式:適配器模式Adapter。分享給大家供大家參考,具體如下:
1. 概述:
接口的改變,是一個需要程序員們必須(雖然很不情愿)接受和處理的普遍問題。程序提供者們修改他們的代碼;系統(tǒng)庫被修正;各種程序語言以及相關(guān)庫的發(fā)展和進(jìn)化。
例子1:iphone4,你即可以使用UBS接口連接電腦來充電,假如只有iphone沒有電腦,怎么辦呢?蘋果提供了iphone電源適配器??梢允褂眠@個電源適配器充電。這個iphone的電源適配器就是類似我們說的適配器模式。(電源適配器就是把電源變成需要的電壓,也就是適配器的作用是使得一個東西適合另外一個東西。)
例子2:最典型的例子就是很多功能手機(jī),每一種機(jī)型都自帶有從電器,有一天自帶充電器壞了,而且市場沒有這類型充電器可買了。怎么辦?萬能充電器就可以解決。這個萬能充電器就是適配器。
2. 問題
你如何避免因外部庫的API改變而帶來的不便?假如你寫了一個庫,你能否提供一種方法允許你軟件的現(xiàn)有用戶進(jìn)行完美地升級,即使你已經(jīng)改變了你的API?為了更好地適宜于你的需要,你應(yīng)該如何改變一個對象的接口?
3. 解決方案
適配器(Adapter)模式為對象提供了一種完全不同的接口。你可以運(yùn)用適配器(Adapter)來實(shí)現(xiàn)一個不同的類的常見接口,同時(shí)避免了因升級和拆解客戶代碼所引起的糾紛。
適配器模式(Adapter Pattern),把一個類的接口變換成客戶端所期待的另一種接口, Adapter模式使原本因接口不匹配(或者不兼容)而無法在一起工作的兩個類能夠在一起工作。又稱為轉(zhuǎn)換器模式、變壓器模式、包裝(Wrapper)器模式(把已有的一些類包裝起來,使之能有滿足需要的接口)。
考慮一下當(dāng)(不是假設(shè)!)一個第三方庫的API改變將會發(fā)生什么。過去你只能是咬緊牙關(guān)修改所有的客戶代碼,而情況往往還不那么簡單。你可能正從事一項(xiàng)新的項(xiàng)目,它要用到新版本的庫所帶來的特性,但你已經(jīng)擁有許多舊的應(yīng)用程序,并且它們與以前舊版本的庫交互運(yùn)行地很好。你將無法證明這些新特性的利用價(jià)值,如果這次升級意味著將要涉及到其它應(yīng)用程序的客戶代碼。
4. 分類
共有兩類適配器模式:1.類的適配器模式(采用繼承實(shí)現(xiàn))2.對象適配器(采用對象組合方式實(shí)現(xiàn))
1)類適配器模式 ——適配器繼承自已實(shí)現(xiàn)的類(一般多重繼承)。
Adapter與Adaptee是繼承關(guān)系
1、用一個具體的Adapter類和Target進(jìn)行匹配。結(jié)果是當(dāng)我們想要一個匹配一個類以及所有它的子類時(shí),類Adapter將不能勝任工作
2、使得Adapter可以重定義Adaptee的部分行為,因?yàn)锳dapter是Adaptee的一個子集
3、僅僅引入一個對象,并不需要額外的指針以間接取得adaptee
2)對象適配器模式—— 適配器容納一個它包裹的類的實(shí)例。在這種情況下,適配器調(diào)用被包裹對象的物理實(shí)體。
Adapter與Adaptee是委托關(guān)系
1、允許一個Adapter與多個Adaptee同時(shí)工作。Adapter也可以一次給所有的Adaptee添加功能
2、使用重定義Adaptee的行為比較困難
無論哪種適配器,它的宗旨都是:保留現(xiàn)有類所提供的服務(wù),向客戶提供接口,以滿足客戶的期望。
即在不改變原有系統(tǒng)的基礎(chǔ)上,提供新的接口服務(wù)。
5. 適用性
以下情況使用Adapter模式:
1 • 你想使用一個已經(jīng)存在的類,而它的接口不符合你的需求。
2 • 你想創(chuàng)建一個可以復(fù)用的類,該類可以與其他不相關(guān)的類或不可預(yù)見的類(即那些接口可能不一定兼容的類)協(xié)同工作。
3 •(僅適用于對象Adapter)你想使用一些已經(jīng)存在的子類,但是不可能對每一個都進(jìn)行子類化以匹配它們的接口。對象適配器可以適配它的父類接口。即僅僅引入一個對象,并不需要額外的指針以間接取得adaptee。
6. 結(jié)構(gòu)
類適配器使用多重繼承對一個接口與另一個接口進(jìn)行匹配,如下圖所示:
對象匹配器依賴于對象組合,如下圖所示:
7. 構(gòu)建模式的組成
•目標(biāo)角色(Target):— 定義Client使用的與特定領(lǐng)域相關(guān)的接口。
• 客戶角色(Client):與符合Target接口的對象協(xié)同。
• 被適配橘色(Adaptee):定義一個已經(jīng)存在并已經(jīng)使用的接口,這個接口需要適配。
• 適配器角色(Adapte) :適配器模式的核心。它將對被適配Adaptee角色已有的接口轉(zhuǎn)換為目標(biāo)角色Target匹配的接口。對Adaptee的接口與Target接口進(jìn)行適配.
8. 效果
類適配器和對象適配器有不同的權(quán)衡。
類適配器
• 用一個具體的Adapter類對Adaptee和Target進(jìn)行匹配。結(jié)果是當(dāng)我們想要匹配一個類以及所有它的子類時(shí),類Adapter將不能勝任工作。
• 使得Adapter可以重定義Adaptee的部分行為,因?yàn)锳dapter是Adaptee的一個子類。
• 僅僅引入了一個對象,并不需要額外的指針以間接得到 Adaptee。
對象適配器則
• 允許一個Adapter與多個Adaptee—即Adaptee本身以及它的所有子類(如果有子類的話)—同時(shí)工作。Adapter也可以一次給所有的Adaptee添加功能。
• 使得重定義Adaptee的行為比較困難。這就需要生成Adaptee的子類并且使得Adapter引用這個子類而不是引用Adaptee本身。
使用Adapter模式時(shí)需要考慮的其他一些因素有:
1) Adapter的匹配程度 對Adaptee的接口與Target的接口進(jìn)行匹配的工作量各個Adapter可能不一樣。工作范圍可能是,從簡單的接口轉(zhuǎn)換(例如改變操作名 )到支持完全不同的操作集合。Adapter的工作量取決于Target接口與Adaptee接口的相似程度
2) 可插入的Adapter 當(dāng)其他的類使用一個類時(shí),如果所需的假定條件越少,這個類就更具可復(fù)用性。如果將接口匹配構(gòu)建為一個類,
就不需要假定對其他的類可見的是一個相同的接口。也就是說,接口匹配使得我們可以將自己的類加入到一些現(xiàn)有的系統(tǒng)中去,
而這些系統(tǒng)對這個類的接口可能會有所不同。
3) 使用雙向適配器提供透明操作 使用適配器的一個潛在問題是,它們不對所有的客戶都透明。被適配的對象不再兼容 Adaptee的接口,
因此并不是所有 Adaptee對象可以被使用的地方它都可以被使用。雙向適配器提供了這樣的透明性。
在兩個不同的客戶需要用不同的方式查看同一個對象時(shí),雙向適配器尤其有用。
9. 實(shí)現(xiàn)
類適配器使用的是繼承
讓我們看看當(dāng)API改變時(shí),如何保護(hù)應(yīng)用程序不受影響。
?php
/**
* 類適配器模式
* @author guisu
*
*/
/**
* 目標(biāo)角色
* @version 1.0
*/
class Target {
/**
* 這個方法將來有可能改進(jìn)
*/
public function hello(){
echo 'Hello ';
}
/**
* 目標(biāo)點(diǎn)
*/
public function world(){
echo 'world';
}
}
/**
* Client 程序
*
*/
class Client {
/**
* Main program.
*/
public static function main() {
$Target = new Target();
$Target->hello();
$Target->world();
}
}
Client::main();
?>
我們Target已經(jīng)明確指出hello()方法會在未來的版本中改進(jìn),甚至不被支持或者淘汰。接下來,現(xiàn)在假設(shè)第二版的Target已經(jīng)發(fā)布。一個全新的greet()方法代替了hello()。
?php
/**
* 類適配器模式
* @author guisu
*
*/
/**
* 目標(biāo)角色
* @version 2.0
*/
class Target {
/**
* 這個方法將來有可能繼續(xù)改進(jìn)
*/
public function greet(){
echo 'Greet ';
}
/**
* 目標(biāo)點(diǎn)
*/
public function world(){
echo 'world';
}
}
如果我們繼續(xù)使用原來的client代碼,肯定會報(bào)錯,找不到hello方法。
針對API“升級”的解決辦法就是創(chuàng)建一個適配器(Adapter)。
類適配器使用的是繼承:
?php
/**
* 類適配器模式
* @author guisu
*
*/
/**
* 目標(biāo)角色
* @version 2.0
*/
interface Target {
/**
* 源類的方法:這個方法將來有可能繼續(xù)改進(jìn)
*/
public function hello();
/**
* 目標(biāo)點(diǎn)
*/
public function world();
}
/**
* 源角色:被適配的角色
*/
class Adaptee {
/**
* 源類含有的方法
*/
public function world() {
echo ' world br />';
}
/**
* 加入新的方法
*/
public function greet() {
echo ' Greet ';
}
}
/**
* 類適配器角色
*/
class Adapter extends Adaptee implements Target {
/**
* 源類中沒有world方法,在此補(bǔ)充
*/
public function hello() {
parent::greet();
}
}
/**
* 客戶端程序
*
*/
class Client {
/**
* Main program.
*/
public static function main() {
$adapter = new Adapter();
$adapter->hello();
$adapter->world();
}
}
Client::main();
?>
對象適配器使用的是委派
?php
/**
* 類適配器模式
* @author guisu
*
*/
/**
* 目標(biāo)角色
* @version 2.0
*/
interface Target {
/**
* 源類的方法:這個方法將來有可能繼續(xù)改進(jìn)
*/
public function hello();
/**
* 目標(biāo)點(diǎn)
*/
public function world();
}
/**
* 源角色:被適配的角色
*/
class Adaptee {
/**
* 源類含有的方法
*/
public function world() {
echo ' world br />';
}
/**
* 加入新的方法
*/
public function greet() {
echo ' Greet ';
}
}
/**
* 類適配器角色
*/
class Adapter implements Target {
private $_adaptee;
/**
* construct
*
* @param Adaptee $adaptee
*/
public function __construct(Adaptee $adaptee) {
$this->_adaptee = $adaptee;
}
/**
* 源類中沒有world方法,在此補(bǔ)充
*/
public function hello() {
$this->_adaptee->greet();
}
/**
* 源類中沒有world方法,在此補(bǔ)充
*/
public function world() {
$this->_adaptee->world();
}
}
/**
* 客戶端程序
*
*/
class Client {
/**
* Main program.
*/
public static function main() {
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
$adapter->hello();
$adapter->world();
}
}
Client::main();
?>
如例中代碼所示,你可以運(yùn)用適配器(Adapter)模式來避免因外部庫改變所帶來的不便——倘若向上兼容。作為某個庫的開發(fā)者,你應(yīng)該獨(dú)立編寫適配器,使你的用戶更簡便地使用新版本的庫,而不用去修改他們現(xiàn)有的全部代碼。
GoF書中提出的適配器(Adapter)模式更傾向于運(yùn)用繼承而不是組成。這在強(qiáng)類型語言中是有利的,因?yàn)檫m配器(Adapter)事實(shí)上是一個目標(biāo)類的子類,因而能更好地與類中方法相結(jié)合。
了更好的靈活性,我個人比較傾向于組成的方法(特別是在結(jié)合了依賴性倒置的情況下);盡管如此,繼承的方法提供兩種版本的接口,或許在你的實(shí)際運(yùn)用中反而是一個提高靈活性的關(guān)鍵。
10.適配器模式與其它相關(guān)模式
橋梁模式(bridge模式):橋梁模式與對象適配器類似,但是橋梁模式的出發(fā)點(diǎn)不同:橋梁模式目的是將接口部分和實(shí)現(xiàn)部分分離,從而對它們可以較為容易也相對獨(dú)立的加以改變。而對象適配器模式則意味著改變一個已有對象的接口
裝飾器模式(decorator模式):裝飾模式增強(qiáng)了其他對象的功能而同時(shí)又不改變它的接口。因此裝飾模式對應(yīng)用的透明性比適配器更好。結(jié)果是decorator模式支持遞歸組合,而純粹使用適配器是不可能實(shí)現(xiàn)這一點(diǎn)的。
Facade(外觀模式):適配器模式的重點(diǎn)是改變一個單獨(dú)類的API。Facade的目的是給由許多對象構(gòu)成的整個子系統(tǒng),提供更為簡潔的接口。而適配器模式就是封裝一個單獨(dú)類,適配器模式經(jīng)常用在需要第三方API協(xié)同工作的場合,設(shè)法把你的代碼與第三方庫隔離開來。
適配器模式與外觀模式都是對現(xiàn)相存系統(tǒng)的封裝。但這兩種模式的意圖完全不同,前者使現(xiàn)存系統(tǒng)與正在設(shè)計(jì)的系統(tǒng)協(xié)同工作而后者則為現(xiàn)存系統(tǒng)提供一個更為方便的訪問接口。簡單地說,適配器模式為事后設(shè)計(jì),而外觀模式則必須事前設(shè)計(jì),因?yàn)橄到y(tǒng)依靠于外觀??傊m配器模式?jīng)]有引入新的接口,而外觀模式則定義了一個全新的接口。
代理模式(Proxy )在不改變它的接口的條件下,為另一個對象定義了一個代理。
裝飾者模式,適配器模式,外觀模式三者之間的區(qū)別:
裝飾者模式的話,它并不會改變接口,而是將一個一個的接口進(jìn)行裝飾,也就是添加新的功能。
適配器模式是將一個接口通過適配來間接轉(zhuǎn)換為另一個接口。
外觀模式的話,其主要是提供一個整潔的一致的接口給客戶端。
更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《php面向?qū)ο蟪绦蛟O(shè)計(jì)入門教程》、《PHP數(shù)組(Array)操作技巧大全》、《PHP基本語法入門教程》、《PHP運(yùn)算與運(yùn)算符用法總結(jié)》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫操作入門教程》及《php常見數(shù)據(jù)庫操作技巧匯總》
希望本文所述對大家PHP程序設(shè)計(jì)有所幫助。
您可能感興趣的文章:- PHP設(shè)計(jì)模式(九)外觀模式Facade實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(八)裝飾器模式Decorator實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(七)組合模式Composite實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(六)橋連模式Bridge實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(四)原型模式Prototype實(shí)例詳解【創(chuàng)建型】
- PHP設(shè)計(jì)模式(三)建造者模式Builder實(shí)例詳解【創(chuàng)建型】
- PHP設(shè)計(jì)模式(一)工廠模式Factory實(shí)例詳解【創(chuàng)建型】
- 深入分析PHP設(shè)計(jì)模式