適配器模式——結構型模式(1)


前言

從今天開始,我們繼續設計模式系列學習之旅,完成了對創建型模式的介紹和學習,接下來,我們將着重介紹各種結構型模式,希望大家能一起參與進來,學習、交流和思考。結構型模式涉及如何組合類和對象以獲得更大的結構,同時,在各種結構型模式的實現上基本上遵循優先使用對象組合,而不是類繼承原則,因為在運行時刻改變對象組合關系,可以使對象組合方式更具靈活性,這種機制用靜態類組合(繼承方式)是不可能實現的,這點,在我們學習各種結構型模式的過程中將會有較深刻的體味。好了,就讓我們趕緊進入結構型模式的第一次探索之旅吧——適配器模式。

動機

在日常的系統開發中,我們往往需要借助第三方組件或者是中間件來幫助我們快速完成某一功能模塊,避免重復造輪子。但是,由於系統本身所支持的接口與第三方組件或者中間件所提供的接口不兼容,因此即便它們的功能實現完全符合系統的開發需求,仍不能直接拿來使用。這時候,作為開發人員應該提供一種封裝機制,對現存組件接口進行包裝,使其完全符合我們當前系統的接口要求,這樣便能達到重用已有組件的目的呢。這不僅避免了浪費人力物力的從頭開發,同時還能極大地提高系統的開發效率。面對上述這樣的一種需求,有一種結構型模式便是它的福星——適配器模式,它能夠很好地解決由於接口不兼容而導致不能直接運用於新系統的棘手問題。

意圖

將一個類的接口轉換成客戶希望的另外一個接口。適配器模式使復原本由於接口不兼容而不能一起工作的那些類可以一起工作。

結構圖

適配器模式的實現有兩種方式,一個是通過多重繼承的方式,另外一個是通過對象組合的方式,因此,針對這兩種不同的實現方式,就存在兩種不同的結構圖。

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

image

對象適配器依賴於對象組合,結構圖如下:

image

  1. 目標(Target)角色:定義客戶使用的與特定領域相關的接口,也是客戶所期待的接口。
  2. 被適配對象(Adaptee)角色:一個已經存在的接口,同時實現了系統需要的功能,但是與當前系統接口不兼容,需要被適配的類。
  3. 適配器對象(Adapter)角色:把Adaptee接口轉換成目標接口,也就是對Adaptee接口與Target接口進行適配的類。

代碼示例

類適配器代碼示例:

 1:  public abstract class Target{
 2:      public abstract void Request();
 3:  }
 4:   
 5:  public interface Adaptee{
 6:      public void SpecificRequest();
 7:  }
 8:   
 9:  public class Adapter extends Target implements Adaptee{
10:      public void Request(){
11:          SpecificRequest();
12:      }
13:   
14:      @Override
15:      public void SpecificRequest() {
16:          //省略了Adaptee相關的具體實現
17:          
18:      }
19:  }
20:   
21:  public class Client{
22:      public static void main(String[] args){
23:          Target target=new Adapter();
24:          target.Request();
25:      }
26:  }

 

注意,由於java語言不支持多繼承,因此我們需要將Adaptee修改為接口類型。我們可以看到,通過繼承的方式來實現類適配器,有較多的局限性,我們需要重新在Adapter類中重寫Adaptee中對應的方法,但由於商業原因,我們無法獲知其具體的實現源碼,因此在java語言層面類適配器模式有一定的局限性,沒有對象適配器使用的廣泛和方便。

對象適配器代碼示例:

 1:  public abstract class Target{
 2:      public abstract void Request();
 3:  }
 4:   
 5:  public class Adaptee{
 6:      public void SpecificRequest(){
 7:          //省略具體實現
 8:      }
 9:  }
10:   
11:  public class Adapter extends Target {
12:      private Adaptee adaptee=null;
13:      public Adapter(Adaptee adaptee){
14:          this.adaptee=adaptee;
15:      }
16:      public void Request(){
17:          //直接調用Adaptee對應的方法來完成對應的功能
18:          adaptee.SpecificRequest();
19:      }
20:  }
21:   
22:  public class Client{
23:      public static void main(String[] args){
24:          Target target=new Adapter(new Adaptee());
25:          target.Request();
26:      }
27:  }

根據上述對象適配器的示例代碼,我們可以看到,在適配器類Adapter中的Request()方法中,我們通過對象組合的方式,將對Request()方法的請求直接委托給Adaptee對象的SpecificRequest()方法來完成。通過這樣的方式,我們無需了解adaptee類的源碼,就可以對其進行適配操作呢,相較於類適配器來說,對象適配器是一種更值得推薦的實現方式。下文還會對兩種實現方式作進一步的比較闡述。

現實場景

在實現的生活中,我們處處可以看到適配的場景。比如說我們使用的日用的家用電器,由於美國的家用標准電壓是110v,因此其家電用器的標准電壓也是110v,但是在中國,家用標准電壓卻是220v,如果不”適配“,顯然無法在國內使用美國生產的電器,此時我們通常會使用變壓器,將標准的220v電壓調低到110v狀態,這樣就能正常使用美國國內生產的電器呢,從適配器的角度來看,其實這就是一種適配的過程,通過變壓器將電壓降低,致使原本由於使用電壓不同而不能在國內正常使用的家電現在也能夠很好地使用呢,這其實就是典型的適配器模式。再比如就是外文翻譯,如果你是不懂英文,又需要和美國人交流,那么這時候你必須尋求翻譯家的幫助,將自己的中文表達通過翻譯家轉譯為相應的英文表達,這樣對方才能聽懂接受自己的表達,反之也一樣,同理,此時翻譯家其實就充當着一個適配器的角色,將原本不相通的語言表達進行翻譯促使彼此都能聽懂彼此的表達,更形象點的說就是將彼此的語言表達進行了一層本地化包裝,這樣就能讓原本語言不相通的彼此進行無障礙地交流呢,這是不是也是一種適配器模式的體現呢?

上面所舉的例子是我們日常生活中能夠碰到的例子,作為程序員,在我們日常的開發過程中,適配器模式自然也是家常便飯。就拿java語言來說,我們幾乎每天都和數據庫打交道,但是數據庫類型迥異,每個廠家都會有自己的一套實現標准,如果我們大家需要根據自己當前使用的數據庫類型而選擇對應接口的操作api的話,大家會不會有煩燥到不行呢?至少我是會這樣的。但是事實的情況並並非我剛剛描述的那個樣子,不管我們使用何種數據庫,mysql還是oracle好,我們需要改動的只是替換相應的數據庫驅動程序包而已,但是對各種數據庫操作的接口不管使用哪個數據庫驅動程序都是一致的,這自然省去了我們很多的沒必要的再學習時間。講到這里,大家應該能猜到正是通過使用適配器模式,致使各大廠商提供的數據庫驅動程序對各個數據庫操作的api接口都完全一致,完全屏蔽了具體數據庫底層的實現細節,這些細節我們也無需了解。說了這么多,接下來,還是讓我們通過代碼的形式來演繹下上面所介紹的場景吧!

 1:  public abstract class Target{
 2:      public abstract void select();
 3:      public abstract void insert();
 4:      public abstract void update();
 5:      public abstract void delete();
 6:  }
 7:   
 8:  public class MySql{
 9:      public void select_mysql(){}//mysql底層對select操作的實現api
10:      public void insert_mysql(){}//mysql底層對insert操作的實現api
11:      public void update_mysql(){}//mysql底層對update操作的實現api
12:      public void delete_mysql(){}//mysql底層對delete操作的實現api
13:  }    
14:   
15:  public class MySqlAdapter extends Target {
16:      private MySql adaptee=null;
17:      public  MySqlAdapter(MySql adaptee){
18:          this.adaptee=adaptee;
19:      }
20:      public void select(){
21:          //直接調用Adaptee對應的方法來完成對應的功能
22:          adaptee.select_mysql();
23:      }
24:      public void insert(){
25:          adaptee.insert_mysql();
26:      }
27:      public void update(){
28:          adaptee.update_mysql();
29:      }
30:      public void delete(){
31:          adaptee.delete_mysql();
32:      }
33:  }
34:   
35:  public class Client{
36:      public static void main(String[] args){
37:          Target target=new MySqlAdapter(new MySql());
38:          target.select();
39:          target.insert();
40:          target.update();
41:          target.delete();
42:      }
43:  }    

 上述示例代碼中,我們通過適配器來適配mysql底層操作api,也就是crud操作,將它們包裝成統一規范的接口形式,方便客戶端調用。同樣的道理,如果是oracle數據庫,也盡可以通過對象適配器模式來對其底層api進行適配包裝成符合統一規范的數據庫驅動程序。好呢,對適配器模式的現實場景就講解到這呢,大家都發揮聰明才智,盡情聯想生活中的種種符合適配器模式的場景,也歡迎留言交流!

實現要點

  1. 適配器模式主要有兩種實現結構:類適配器模式和對象適配器模式。類適配器模式通過多重繼承的方式來實現,容易產生緊耦合,一般不推薦使用,而對象適配器械采用對象組合的方式,具有更弱的耦合性,有利於代碼的可擴展性和可維護性。
  2. 適配器模式主要應用於”希望復用一些已經存在的類,而接口又與復用環境所要求的接口不一致的情況“,在遺留代碼復用、類庫遷移等方面非常有用。
  3. 想要真正地發揮適配器模式魔力,本身就要求我們在編程的過程中盡量遵循”面向接口編程“原則,這樣在后期或者今后的適配上才更具靈活性。
  4. 適配器模式在實現上非常靈活,我們完全可以將現在的對象作為復用環境下新接口的參數,在實現體里完成對現存對象的適配過程。

運用效果

針對類適配器(C++更適合實現類適配器,因為它支持多繼承,java或者.net並不擅長):

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

針對對象適配器

  1. 允許一個Adapter與多個Adaptee——即Adaptee本身以及它的所有子類(如果有子類 的話)同時工作。Adapter也可以一次給所有的Adaptee添加功能。
  2. 使得重定義Adaptee的行為比較困難。這就需要生成Adaptee的子類並且便得Adapter引用這個子類而不是引用Adaptee本身。

總得說來,使用適配器模式會帶來更好的復用性可擴展性;但如果過度使用適配器的話,也會讓系統非常零亂,不容易整體進行把握,過猶則不及嘛!

適用性

  1. 你想使用一個已經存在的類,而它的接口並不符合你當前系統接口需要時。
  2. 你想創建一個可以復用的類,該類可以與其他不相關的類或者不可預見的類(即那些接口可能不一定兼容的類)協同工作時。
  3. (僅適用於對象適配器)你想使用一些已經存在的子類,但是不可能對每一個都進行子類化以匹配它們的的接口時。對象適配器可以適配它的父類接口。

相關模式

  1. 適配器模式和橋接模式:兩者結構比較相似,功能卻完全不同。適配器是把兩個或者多個接口的功能進行轉換匹配;而橋接模式是讓接口與實現相分離,以便它們可以相對獨立地變化(這里不理解的同學,可以暫時略過,下篇即將重點介紹橋接模式)。
  2. 適配器模式與裝飾模式:兩者都是使用對象組合的方式來實現,但是兩者的形式卻完全不一樣:一般來說適配器適配之后是需要改變接口的,否則就沒有適配的必要呢;而裝飾模式是不會改變接口的,不管出現多少層裝飾都是一個接口,這樣帶來的一個好處便是很容易做到遞歸組合,對一個對象進行多次裝飾便其具有多重裝飾功能(這里不理解的同學,可以暫時略過,后面會有對裝飾模式的詳細介紹)。
  3. 適配器模式和代理模式:在實現適配器的時候,可以通過代理來調用adaptee,這樣可以獲得更大的靈活性。
  4. 適配器和抽象工廠模式:在對象適配器模式的實現中,我們需要引用adaptee對象,而這個對象的創建工作,我們可以利用創建型模式來完成,比如工廠方法模式、抽象工廠或者單例模式等。

總結

適配器模式的本質是:轉換匹配,復用功能。適配器通過轉換調用已有對象的實現,從而把已有的功能匹配成需要的接口形式,這樣便滿足復用環境接口需要。也就是說轉換匹配是手段,而復用已有的功能才是目的。最后,需要提醒大家的是,適配器模式是事后補救的一種措施,如果是為了對其三方組件功能的復用,這不可厚非,但是如果只是在項目后期使用適配器模式對系統某個功能模塊進行適配的話,或許大家就應該靜下心來,好好思考下,是否在系統設計上本身就存在缺陷或者說考慮不夠周到的地方,因為畢竟事先做好設計,總比事后修改要來得輕松方便些,運用適配器模式並不是解決所有遺留問題。不過總的說來,適配器模式確實不失為一個達到代碼功能復用的好方法,好手段,大家說呢?個中利弊就仁者見仁,智者見智呢!適配器模式就介紹到這吧,下篇繼續講解另一個結構型模式——橋接模式,敬請期待!

 

參考資料

  1. 程傑著《大話設計模式》一書
  2. 陳臣等著《研磨設計模式》一書
  3. GOF著《設計模式》一書
  4. Terrylee .Net設計模式系列文章
  5. 呂震宇老師 設計模式系列文章


免責聲明!

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



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