<a target="_blank" href="http://blog.csdn.net/hguisu/article/details/7527842">設計模式(五)擴充卡模式adapter(結構型)</a>
1. 概述:
例子1:iphone4,你即可以使用ubs接口連接配接電腦來充電,假如隻有iphone沒有電腦,怎麼辦呢?蘋果提供了iphone電源擴充卡。可以使用這個電源擴充卡充電。這個iphone的電源擴充卡就是類似我們說的擴充卡模式。(電源擴充卡就是把電源變成需要的電壓,也就是擴充卡的作用是使得一個東西适合另外一個東西。) 例子2:最典型的例子就是很多功能手機,每一種機型都自帶有從電器,有一天自帶充電器壞了,而且市場沒有這類型充電器可買了。怎麼辦?萬能充電器就可以解決。這個萬能充電器就是擴充卡。
2. 問題
你如何避免因外部庫的api改變而帶來的不便?假如你寫了一個庫,你能否提供一種方法允許你軟體的現有使用者進行完美地更新,即使你已經改變了你的api?為了更好地适宜于你的需要,你應該如何改變一個對象的接口?
3. 解決方案
考慮一下當(不是假設!)一個第三方庫的api改變将會發生什麼。過去你隻能是咬緊牙關修改所有的客戶代碼,而情況往往還不那麼簡單。你可能正從事一項新的項目,它要用到新版本的庫所帶來的特性,但你已經擁有許多舊的應用程式,并且它們與以前舊版本的庫互動運作地很好。你将無法證明這些新特性的利用價值,如果這次更新意味着将要涉及到其它應用程式的客戶代碼。
4. 分類
共有兩類擴充卡模式:1.類的擴充卡模式(采用繼承實作)2.對象擴充卡(采用對象組合方式實作) ——擴充卡繼承自已實作的類(一般多重繼承)。 adapter與adaptee是繼承關系 1、用一個具體的adapter類和target進行比對。結果是當我們想要一個比對一個類以及所有它的子類時,類adapter将不能勝任工作 2、使得adapter可以重定義adaptee的部分行為,因為adapter是adaptee的一個子集 3、僅僅引入一個對象,并不需要額外的指針以間接取得adaptee 擴充卡容納一個它包裹的類的執行個體。在這種情況下,擴充卡調用被包裹對象的實體實體。 adapter與adaptee是委托關系 1、允許一個adapter與多個adaptee同時工作。adapter也可以一次給所有的adaptee添加功能 2、使用重定義adaptee的行為比較困難 無論哪種擴充卡,它的宗旨都是:保留現有類所提供的服務,向客戶提供接口,以滿足客戶的期望。 即在不改變原有系統的基礎上,提供新的接口服務。
5. 适用性
1 • 你想使用一個已經存在的類,而它的接口不符合你的需求。 2 • 你想建立一個可以複用的類,該類可以與其他不相關的類或不可預見的類(即那些接口可能不一定相容的類)協同工作。
6. 結構
類擴充卡使用多重繼承對一個接口與另一個接口進行比對,如下圖所示: 對象比對器依賴于對象組合,如下圖所示:
7. 構模組化式的組成
8. 效果
類擴充卡和對象擴充卡有不同的權衡。 <a target="_blank" href="http://blog.csdn.net/hguisu/article/details/7527842">類擴充卡</a> • 用一個具體的adapter類對adaptee和target進行比對。結果是當我們想要比對一個類以及所有它的子類時,類adapter将不能勝任工作。 • 使得adapter可以重定義adaptee的部分行為,因為adapter是adaptee的一個子類。 • 僅僅引入了一個對象,并不需要額外的指針以間接得到 adaptee。 對象擴充卡則 • 允許一個adapter與多個adaptee—即adaptee本身以及它的所有子類(如果有子類的話)—同時工作。adapter也可以一次給所有的adaptee添加功能。 • 使得重定義adaptee的行為比較困難。這就需要生成adaptee的子類并且使得adapter引用這個子類而不是引用adaptee本身。 使用adapter模式時需要考慮的其他一些因素有: 1) adapter的比對程度 對adaptee的接口與target的接口進行比對的工作量各個adapter可能不一樣。工作範圍可能是,從簡單的接口轉換(例如改變操作名 )到支援完全不同的操作集合。adapter的工作量取決于target接口與adaptee接口的相似程度 2) 可插入的adapter 當其他的類使用一個類時,如果所需的假定條件越少,這個類就更具可複用性。如果将接口比對建構為一個類, 就不需要假定對其他的類可見的是一個相同的接口。也就是說,接口比對使得我們可以将自己的類加入到一些現有的系統中去, 而這些系統對這個類的接口可能會有所不同。 3) 使用雙向擴充卡提供透明操作 使用擴充卡的一個潛在問題是,它們不對所有的客戶都透明。被适配的對象不再相容 adaptee的接口, 是以并不是所有 adaptee對象可以被使用的地方它都可以被使用。雙向擴充卡提供了這樣的透明性。 在兩個不同的客戶需要用不同的方式檢視同一個對象時,雙向擴充卡尤其有用。
9. 實作
類擴充卡使用的是繼承 讓我們看看當api改變時,如何保護應用程式不受影響。 <?php /** * 類擴充卡模式 * @author guisu * */ * 目标角色 * @version 1.0 class target { /** * 這個方法将來有可能改進 */ public function hello(){ echo 'hello '; } * 目标點 public function world(){ echo 'world'; } * client 程式 * class client { * main program. public static function main() { $target = new target(); $target->hello(); $target->world(); client::main(); ?>
我們target已經明确指出hello()方法會在未來的版本中改進,甚至不被支援或者淘汰。接下來,現在假設第二版的target已經釋出。一個全新的greet()方法代替了hello()。 * @version 2.0 * 這個方法将來有可能繼續改進 public function greet(){ echo 'greet '; 如果我們繼續使用原來的client代碼,肯定會報錯,找不到hello方法。 針對api“更新”的解決辦法就是建立一個擴充卡(adapter)。 類擴充卡使用的是繼承: interface target { * 源類的方法:這個方法将來有可能繼續改進 public function hello(); public function world(); * 源角色:被适配的角色 class adaptee { * 源類含有的方法 public function world() { echo ' world <br />'; * 加入新的方法 public function greet() { echo ' greet '; * 類擴充卡角色 class adapter extends adaptee implements target { * 源類中沒有world方法,在此補充 public function hello() { parent::greet(); * 用戶端程式 $adapter = new adapter(); $adapter->hello(); $adapter->world(); 對象擴充卡使用的是委派 class adapter implements target { private $_adaptee; * construct * * @param adaptee $adaptee public function __construct(adaptee $adaptee) { $this->_adaptee = $adaptee; $this->_adaptee->greet(); $this->_adaptee->world(); $adaptee = new adaptee(); $adapter = new adapter($adaptee); gof書中提出的擴充卡(adapter)模式更傾向于運用繼承而不是組成。這在強類型語言中是有利的,因為擴充卡(adapter)事實上是一個目标類的子類,因而能更好地與類中方法相結合。 了更好的靈活性,我個人比較傾向于組成的方法(特别是在結合了依賴性倒置的情況下);盡管如此,繼承的方法提供兩種版本的接口,或許在你的實際運用中反而是一個提高靈活性的關鍵。
擴充卡模式與外觀模式都是對現相存系統的封裝。但這兩種模式的意圖完全不同,前者使現存系統與正在設計的系統協同工作而後者則為現存系統提供一個更為友善的通路接口。簡單地說,擴充卡模式為事後設計,而外觀模式則必須事前設計,因為系統依靠于外觀。總之,擴充卡模式沒有引入新的接口,而外觀模式則定義了一個全新的接口。 裝飾者模式,擴充卡模式,外觀模式三者之間的差別: 裝飾者模式的話,它并不會改變接口,而是将一個一個的接口進行裝飾,也就是添加新的功能。 擴充卡模式是将一個接口通過适配來間接轉換為另一個接口。 外觀模式的話,其主要是提供一個整潔的一緻的接口給用戶端。