【原】從頭學習設計模式(七)——適配器模式

一、引入
在系統開發中,我們常常能遇到以下的幾種場景:
1.有一個舊系統,我們想要通過重構的方式,對舊系統進行一些升級,但舊系統內部的依賴性和復雜性很高,可謂是“牽一發而動全身”啊,怎么將影響減到最小?
2.我們要開發一套新的系統去集成現有的舊系統,舊系統中的代碼和功能不允許改變,新系統如何去適配舊系統的接口完成對接?
3.購買了第三方的成熟組件,如何在自己的系統中有效的和第三方組件作接口?
要解決以上的這些問題,我們可以考慮使用適配器模式來處理。當然你可以看出來這些場景都是在不得已的情況下的補救措施,是無可奈何時之舉,正常設計系統時還是要好好考慮未來的擴展性和可維護性。
來看一下適配器模式的標準定義是怎樣的。
適配器模式(Adaptor):將一個類的接口轉換成客戶希望的另外一個接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
舉個生活中的例子,你想把一個PS/2接口的鍵盤,外接到筆記本上,怎么辦呢?眾所周知,筆記本上沒有PS/2接口的,只有USB接口。好在,市面上有一種PS2轉USB的轉接器,兩邊簡單對接就可以正常用了~

這其實就是適配器模式的原理,將使兩邊接口統一化。
二、類圖

下面來看看適配器模式的組成吧。
1) 目標(Target)角色:定義Client 使用的接口。
2) 被適配(Adaptee)角色:這個角色有一個已存在并使用了的接口,而這個接口是需要我們適配的。
3) 適配器(Adapter)角色:這個適配器模式的核心。它將被適配角色已有的接口轉換為目標角色希望的接口。
三、類適配器與對象適配器
適配器從實現方式上可以分為兩種:類適配器和對象適配器。兩者的區別在于,前者采用繼承,后者采用組合。
首先,我們來看一下類適配器是個什么樣的。
我們有一個新的接口 NewInterface,但是舊系統中的一個類沒有實現這個接口,我們用繼承這個類,同時實現這個新接口的方法,構造出一個適配器類 Adaptor, Adaptor統一了接口,客戶端可以構造一個NewInterface類型的Adaptor實例對象了,這個實例對象同時具有舊類的功能,又符合了接口規范。
1 /// <summary> 2 /// 新接口 3 /// </summary> 4 public interface NewInterface 5 { 6 void WriteMessage(); 7 } 8 9 /// <summary> 10 /// 舊類(與新接口無法對接) 11 /// </summary> 12 public class OldClass 13 { 14 public void Message() 15 { 16 Console.WriteLine("這是舊類的原方法。"); 17 } 18 } 19 20 /// <summary> 21 /// 適配器 22 /// 繼承舊類,并實現新接口 23 /// </summary> 24 public class Adpter : OldClass,NewInterface 25 { 26 public void WriteMessage() 27 { 28 base.Message(); 29 Console.WriteLine("這是適配后的新方法。"); 30 } 31 }
模擬客戶端調用:
1 static void Main(string[] args) 2 { 3 NewInterface a=new Adpter(); 4 a.WriteMessage(); 5 6 Console.ReadKey(); 7 }
執行結果顯示,舊類的方法和新的接口方法都被調用了。

這就是類適配器的實現方法,但是你是否發現了問題呢? 如果我需要一個適配器去包裝多個舊類怎么辦?在C#中是不支持類的多重繼承的,所以下面的寫法是無法通過編譯的:
public class Adpter : OldClass1,OldClass2,OldClass3,NewInterface
若要解決這個問題,我們可以考慮另外一種實現方式:對象適配器
實現的思路也很簡單,我們不采用繼承了,只去實現一下新的接口,然后把所有要適配的舊類作為適配器類的成員對象包含到內部就可以了。
保持其他部分的代碼不變,我們只修改一下Adpter這個類,代碼如下:
1 /// <summary> 2 /// 適配器 3 /// 只實現接口,內部創建舊類的實例對象成員 4 /// </summary> 5 public class Adpter : NewInterface 6 { 7 private OldClass oldClass=new OldClass(); 8 public void WriteMessage() 9 { 10 oldClass.Message(); 11 Console.WriteLine("這是適配后的新方法。"); 12 } 13 }
執行結果是一樣的。

四、特殊適配器與缺省適配器
另外,從使用適配器的目的性上講,又可以分成特殊適配器和缺省適配器。前者是為了復用原有代碼并適配當前接口,我們上面的例子都是特殊適配器。缺省適配器是一種缺省實現的一種方法,也就是避免子類為了保持繼承的完整性,不得不去實現一些方法,但方法體為空。
前面說過,適配器其實是一種補救的方式,那我們研究一下是什么原因導致我們要用缺省適配器的方法。
在設計模式的六大原則里有一個叫作“最小接口原則”,就是接口的設計要足夠小,職責要足夠單一,比如一個手機接口包含“打電話”和“發短信”的方法是合理,如果同時包含“打飛機”這個方法,就違背了“最小接口原則”了,因為“打飛機”并不是一個手機通用的功能,我們應該再設計一個智能手機接口去單獨包含它。
這個原則搞明白以后,你肯定就知道問題的來源了,沒錯,就是因為接口功能不夠單一,導致接口過大,而繼承他的子類可能并不能提供這樣的功能,只能出現方法空著的情況了。
比如我們已經有了如下設計不合理的接口:
1 public interface Phone 2 { 3 //打電話 4 void Call(); 5 //發短信 6 void Message(); 7 //玩打飛機 8 void Plane(); 9 }
下面,比如我們要構造一個老年機,代碼如下:
1 /// <summary> 2 /// 老年機 3 /// </summary> 4 public class OldPeoplePhone : Phone 5 { 6 public void Call() 7 { 8 Console.WriteLine("老年機可以打電話"); 9 } 10 11 public void Message() 12 { 13 Console.WriteLine("老年機可以發短信"); 14 } 15 16 public void Plane() 17 { 18 //老年機打不了飛機,沒有具體實現 19 } 20 }
你看到了Plane()沒有什么可以實現的,但為了繼承接口,這個空方法必須擺在那。
為了比較好的解決這個問題呢,我們來看一下缺省適配器如何工作。
1 public class PhoneAdaptor : Phone 2 { 3 public virtual void Call() 4 { 5 //留空,子類如果實現可以重寫 6 } 7 8 public virtual void Message() 9 { 10 //留空,子類如果實現可以重寫 11 } 12 13 public virtual void Plane() 14 { 15 //留空,子類如果實現可以重寫 16 } 17 }
1 /// <summary> 2 /// 老年機 3 /// </summary> 4 public class OldPeoplePhone : PhoneAdaptor 5 { 6 public override void Call() 7 { 8 Console.WriteLine("老年機可以打電話"); 9 } 10 public override void Message() 11 { 12 Console.WriteLine("老年機可以發短信"); 13 } 14 }
以后,子類只要繼承自適配器類,去實現自己的方法就可以了,避免了書寫和保留大量的空方法。
五、總結
適配器模式是一種補救手段,是為了復用已有類的代碼并且將其適配到客戶端需要的接口上去。
1.第一種類適配器,由于C#不支持多重類繼承的方式,所以適用的范圍有限。
2.第二種對象適配器,當要適配的對象多于一個的時候考慮使用。
3.第三種缺省適配器,是為了彌補接口過大的歷史問題而導致的不得不去實現所有方法,但很多方法只能留空的情況。
好了,本次適配器模式的分享就到此結束了,如果理解有誤請不吝賜教,共同提高~ 謝謝您的收看。


浙公網安備 33010602011771號