Adapter(適配器)模式


1. 概述:

         接口的改變,是一個需要程序員們必須(雖然很不情願)接受和處理的普遍問題。程序提供者們修改他們的代碼;系統庫被修正;各種程序語言以及相關庫的發展和進化。

        例子1:iphone4,你即可以使用UBS接口連接電腦來充電,假如只有iphone沒有電腦,怎么辦呢?蘋果提供了iphone電源適配器。可以使用這個電源適配器充電。這個iphone的電源適配器就是類似我們說的適配器模式。(電源適配器就是把電源變成需要的電壓,也就是適配器的作用是使得一個東西適合另外一個東西。)

       例子2:最典型的例子就是很多功能手機,每一種機型都自帶有從電器,有一天自帶充電器壞了,而且市場沒有這類型充電器可買了。怎么辦?萬能充電器就可以解決。這個萬能充電器就是適配器。

 

2. 問題

 

     你如何避免因外部庫的API改變而帶來的不便?假如你寫了一個庫,你能否提供一種方法允許你軟件的現有用戶進行完美地升級,即使你已經改變了你的API?為了更好地適宜於你的需要,你應該如何改變一個對象的接口?

 

3. 解決方案

 

        適配器(Adapter)模式為對象提供了一種完全不同的接口。你可以運用適配器(Adapter)來實現一個不同的類的常見接口,同時避免了因升級和拆解客戶代碼所引起的糾紛。

 

    適配器模式(Adapter Pattern),把一個類的接口變換成客戶端所期待的另一種接口, Adapter模式使原本因接口不匹配(或者不兼容)而無法在一起工作的兩個類能夠在一起工作。又稱為轉換器模式、變壓器模式、包裝(Wrapper)器模式(把已有的一些類包裝起來,使之能有滿足需要的接口)。

 

 

     考慮一下當(不是假設!)一個第三方庫的API改變將會發生什么。過去你只能是咬緊牙關修改所有的客戶代碼,而情況往往還不那么簡單。你可能正從事一項新的項目,它要用到新版本的庫所帶來的特性,但你已經擁有許多舊的應用程序,並且它們與以前舊版本的庫交互運行地很好。你將無法證明這些新特性的利用價值,如果這次升級意味着將要涉及到其它應用程序的客戶代碼。

4. 分類

共有兩類適配器模式:1.類的適配器模式(采用繼承實現)2.對象適配器(采用對象組合方式實現)

1)類適配器模式    ——適配器繼承自已實現的類(一般多重繼承)。

 

Adapter與Adaptee是繼承關系
1、用一個具體的Adapter類和Target進行匹配。結果是當我們想要一個匹配一個類以及所有它的子類時,類Adapter將不能勝任工作
2、使得Adapter可以重定義Adaptee的部分行為,因為Adapter是Adaptee的一個子集
3、僅僅引入一個對象,並不需要額外的指針以間接取得adaptee

2)對象適配器模式—— 適配器容納一個它包裹的類的實例。在這種情況下,適配器調用被包裹對象的物理實體。

 

Adapter與Adaptee是委托關系
1、允許一個Adapter與多個Adaptee同時工作。Adapter也可以一次給所有的Adaptee添加功能
2、使用重定義Adaptee的行為比較困難
無論哪種適配器,它的宗旨都是:保留現有類所提供的服務,向客戶提供接口,以滿足客戶的期望。
即在不改變原有系統的基礎上,提供新的接口服務。

5. 適用性

 

以下情況使用 Adapter模式
1 • 你想使用一個已經存在的類,而它的接口不符合你的需求。
2 • 你想創建一個可以復用的類,該類可以與其他不相關的類或不可預見的類(即那些接口可能不一定兼容的類)協同工作。
3 •(僅適用於對象 Adapter)你想使用一些已經存在的子類,但是不可能對每一個都進行子類化以匹配它們的接口。對象適配器可以適配它的父類接口。即僅僅引入一個對象,並不需要額外的指針以間接取得adaptee。

 

6. 結構

類適配器使用多重繼承對一個接口與另一個接口進行匹配,如下圖所示:


對象匹配器依賴於對象組合,如下圖所示:


7. 構建模式的組成

目標角色(Target):— 定義Client使用的與特定領域相關的接口。
 客戶角色(Client):與符合Target接口的對象協同。
• 被適配角色(Adaptee):定義一個已經存在並已經使用的接口,這個接口需要適配。
 適配器角色(Adapte) :適配器模式的核心。它將對被適配Adaptee角色已有的接口轉換為目標角色Target匹配的接口。對Adaptee的接口與Target接口進行適配.

8. 效果

類適配器和對象適配器有不同的權衡。
類適配器
• 用一個具體的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改變時,如何保護應用程序不受影響。

 

  1. <?php  
  2. /** 
  3.  * 類適配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目標角色 
  10.  * @version 1.0 
  11.  */  
  12. class Target {  
  13.    
  14.     /** 
  15.      * 這個方法將來有可能改進 
  16.      */  
  17.     public function hello(){  
  18.         echo 'Hello ';  
  19.     }  
  20.    
  21.     /** 
  22.      * 目標點 
  23.      */  
  24.     public function world(){  
  25.         echo 'world';  
  26.     }  
  27. }  
  28.    
  29. /** 
  30.  * Client 程序 
  31.  * 
  32.  */  
  33. class Client {  
  34.   
  35.     /** 
  36.      * Main program. 
  37.      */  
  38.     public static function main() {  
  39.         $Target = new Target();  
  40.         $Target->hello();  
  41.         $Target->world();  
  42.    
  43.     }  
  44.    
  45. }  
  46. Client::main();  
  47. ?>  

 

 

我們Target已經明確指出hello()方法會在未來的版本中改進,甚至不被支持或者淘汰。接下來,現在假設第二版的Target已經發布。一個全新的greet()方法代替了hello()。

 

  1. <?php  
  2. /** 
  3.  * 類適配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目標角色 
  10.  * @version 2.0 
  11.  */  
  12. class Target {  
  13.    
  14.     /** 
  15.      * 這個方法將來有可能繼續改進 
  16.      */  
  17.     public function greet(){  
  18.         echo 'Greet ';  
  19.     }  
  20.    
  21.     /** 
  22.      * 目標點 
  23.      */  
  24.     public function world(){  
  25.         echo 'world';  
  26.     }  
  27. }  
如果我們繼續使用原來的client代碼,肯定會報錯,找不到hello方法。

 

 

針對API“升級”的解決辦法就是創建一個適配器(Adapter)。

 

類適配器使用的是繼承

  1. <?php  
  2. /** 
  3.  * 類適配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目標角色 
  10.  * @version 2.0 
  11.  */  
  12. interface Target {  
  13.    
  14.     /** 
  15.      * 源類的方法:這個方法將來有可能繼續改進 
  16.      */  
  17.     public function hello();  
  18.    
  19.     /** 
  20.      * 目標點 
  21.      */  
  22.     public function world();  
  23. }  
  24.    
  25. /** 
  26.  * 源角色:被適配的角色 
  27.  */  
  28. class Adaptee {  
  29.     /** 
  30.      * 源類含有的方法 
  31.      */  
  32.     public function world() {  
  33.         echo ' world <br />';  
  34.     }  
  35.    
  36.     /** 
  37.      * 加入新的方法 
  38.      */  
  39.     public function greet() {  
  40.         echo ' Greet ';  
  41.     }  
  42. }  
  43.    
  44. /** 
  45.  * 類適配器角色 
  46.  */  
  47. class Adapter extends Adaptee implements Target {  
  48.    
  49.     /** 
  50.      * 源類中沒有world方法,在此補充 
  51.      */  
  52.     public function hello() {  
  53.        parent::greet();  
  54.     }  
  55.    
  56. }  
  57. /** 
  58.  * 客戶端程序 
  59.  * 
  60.  */  
  61. class Client {  
  62.    
  63.     /** 
  64.      * Main program. 
  65.      */  
  66.     public static function main() {  
  67.         $adapter = new Adapter();  
  68.         $adapter->hello();  
  69.         $adapter->world();  
  70.     }  
  71. }  
  72. Client::main();  
  73. ?>  

對象適配器使用的是委派
  1. <?php  
  2. /** 
  3.  * 類適配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目標角色 
  10.  * @version 2.0 
  11.  */  
  12. interface Target {  
  13.    
  14.     /** 
  15.      * 源類的方法:這個方法將來有可能繼續改進 
  16.      */  
  17.     public function hello();  
  18.    
  19.     /** 
  20.      * 目標點 
  21.      */  
  22.     public function world();  
  23. }  
  24.    
  25. /** 
  26.  * 源角色:被適配的角色 
  27.  */  
  28. class Adaptee {  
  29.     /** 
  30.      * 源類含有的方法 
  31.      */  
  32.     public function world() {  
  33.         echo ' world <br />';  
  34.     }  
  35.    
  36.     /** 
  37.      * 加入新的方法 
  38.      */  
  39.     public function greet() {  
  40.         echo ' Greet ';  
  41.     }  
  42. }  
  43.    
  44. /** 
  45.  * 類適配器角色 
  46.  */  
  47. class Adapter  implements Target {  
  48.   
  49.     private $_adaptee;  
  50.     /** 
  51.      * construct 
  52.      * 
  53.      * @param Adaptee $adaptee 
  54.      */  
  55.     public function __construct(Adaptee $adaptee) {  
  56.         $this->_adaptee = $adaptee;  
  57.     }  
  58.    
  59.     /** 
  60.      * 源類中沒有world方法,在此補充 
  61.      */  
  62.     public function hello() {  
  63.        $this->_adaptee->greet();  
  64.     }  
  65.    
  66.     /** 
  67.      * 源類中沒有world方法,在此補充 
  68.      */  
  69.     public function world() {  
  70.        $this->_adaptee->world();  
  71.     }  
  72. }  
  73. /** 
  74.  * 客戶端程序 
  75.  * 
  76.  */  
  77. class Client {  
  78.    
  79.     /** 
  80.      * Main program. 
  81.      */  
  82.     public static function main() {  
  83.         $adaptee = new Adaptee();  
  84.         $adapter = new Adapter($adaptee);  
  85.         $adapter->hello();  
  86.         $adapter->world();  
  87.     }  
  88. }  
  89.   
  90. Client::main();  
  91. ?>  

 

如例中代碼所示,你可以運用適配器(Adapter)模式來避免因外部庫改變所帶來的不便——倘若向上兼容。作為某個庫的開發者,你應該獨立編寫適配器,使你的用戶更簡便地使用新版本的庫,而不用去修改他們現有的全部代碼。

     GoF書中提出的適配器(Adapter)模式更傾向於運用繼承而不是組成。這在強類型語言中是有利的,因為適配器(Adapter)事實上是一個目標類的子類,因而能更好地與類中方法相結合。

了更好的靈活性,我個人比較傾向於組成的方法(特別是在結合了依賴性倒置的情況下);盡管如此,繼承的方法提供兩種版本的接口,或許在你的實際運用中反而是一個提高靈活性的關鍵。

10.適配器模式與其它相關模式

橋梁模式(bridge模式)橋梁模式與對象適配器類似,但是橋梁模式的出發點不同:橋梁模式目的是將接口部分和實現部分分離,從而對它們可以較為容易也相對獨立的加以改變。而對象適配器模式則意味着改變一個已有對象的接口

裝飾器模式(decorator模式):裝飾模式增強了其他對象的功能而同時又不改變它的接口。因此裝飾模式對應用的透明性比適配器更好。結果是decorator模式支持遞歸組合,而純粹使用適配器是不可能實現這一點的。

Facade(外觀模式)適配器模式的重點是改變一個單獨類的API。Facade的目的是給由許多對象構成的整個子系統,提供更為簡潔的接口。而適配器模式就是封裝一個單獨類,適配器模式經常用在需要第三方API協同工作的場合,設法把你的代碼與第三方庫隔離開來。

適配器模式與外觀模式都是對現相存系統的封裝。但這兩種模式的意圖完全不同,前者使現存系統與正在設計的系統協同工作而后者則為現存系統提供一個更為方便的訪問接口。簡單地說,適配器模式為事后設計,而外觀模式則必須事前設計,因為系統依靠於外觀。總之,適配器模式沒有引入新的接口,而外觀模式則定義了一個全新的接口

 

代理模式(Proxy )在不改變它的接口的條件下,為另一個對象定義了一個代理。

 

裝飾者模式,適配器模式,外觀模式三者之間的區別:

裝飾者模式的話,它並不會改變接口,而是將一個一個的接口進行裝飾,也就是添加新的功能。

適配器模式是將一個接口通過適配來間接轉換為另一個接口。

外觀模式的話,其主要是提供一個整潔的一致的接口給客戶端。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM