一、引言
在實際的開發過程中,由於應用環境的變化(例如使用語言的變化),我們需要的實現在新的環境中沒有現存物件可以滿足,但是其他環境卻存在這樣現存的物件。那麼如果將“將現存的物件”在新的環境中進行呼叫呢?解決這個問題的辦法就是我們本文要介紹的介面卡模式——使得新環境中不需要去重複實現已經存在了的實現而很好地把現有物件(指原來環境中的現有物件)加入到新環境來使用。
二、介面卡模式的詳細介紹
2.1 定義
下面讓我們看看介面卡的定義,介面卡模式——把一個類的介面變換成客戶端所期待的另一種介面,從而使原本介面不匹配而無法一起工作的兩個類能夠在一起工作。介面卡模式有類的介面卡模式和物件的介面卡模式兩種形式,下面我們分別討論這兩種形式的實現和給出對應的類圖來幫助大家理清類之間的關係。
2.2 類的介面卡模式實現
在這裡以生活中的一個例子來進行演示介面卡模式的實現,具體場景是: 在生活中,我們買的電器插頭是2個孔的,但是我們買的插座只有三個孔的,此時我們就希望電器的插頭可以轉換為三個孔的就好,這樣我們就可以直接把它插在插座上,此時三個孔插頭就是客戶端期待的另一種介面,自然兩個孔的插頭就是現有的介面,介面卡模式就是用來完成這種轉換的,具體實現程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using System; /// 這裡以插座和插頭的例子來詮釋介面卡模式 /// 現在我們買的電器插頭是2個孔,但是我們買的插座只有3個孔的 /// 這是我們想把電器插在插座上的話就需要一個電介面卡 namespace 設計模式之介面卡模式 { /// <summary> /// 客戶端,客戶想要把2個孔的插頭 轉變成三個孔的插頭,這個轉變交給介面卡就好 /// 既然介面卡需要完成這個功能,所以它必須同時具體2個孔插頭和三個孔插頭的特徵 /// </summary> class Client { static void Main(string[] args) { // 現在客戶端可以通過電適配要使用2個孔的插頭了 IThreeHole threehole = new PowerAdapter(); threehole.Request(); Console.ReadLine(); } } /// <summary> /// 三個孔的插頭,也就是介面卡模式中的目標角色 /// </summary> public interface IThreeHole { void Request(); } /// <summary> /// 兩個孔的插頭,源角色——需要適配的類 /// </summary> public abstract class TwoHole { public void SpecificRequest() { Console.WriteLine("我是兩個孔的插頭"); } } /// <summary> /// 介面卡類,介面要放在類的後面 /// 介面卡類提供了三個孔插頭的行為,但其本質是呼叫兩個孔插頭的方法 /// </summary> public class PowerAdapter:TwoHole,IThreeHole { /// <summary> /// 實現三個孔插頭介面方法 /// </summary> public void Request() { // 呼叫兩個孔插頭方法 this.SpecificRequest(); } } } |
從上面程式碼中可以看出,客戶端希望呼叫Request方法(即三個孔插頭),但是我們現有的類(即2個孔的插頭)並沒有Request方法,它只有SpecificRequest方法(即兩個孔插頭本身的方法),然而介面卡類(介面卡必須實現三個孔插頭介面和繼承兩個孔插頭類)可以提供這種轉換,它提供了Request方法的實現(其內部呼叫的是兩個孔插頭,因為介面卡只是一個外殼罷了,包裝著兩個孔插頭(因為只有這樣,電器才能使用),並向外界提供三個孔插頭的外觀,)以供客戶端使用。
2.3 類圖
上面實現中,因為介面卡(PowerAdapter類)與源角色(TwoHole類)是繼承關係,所以該介面卡模式是類的介面卡模式,具體對應的類圖為:
2.4 物件的介面卡模式
上面都是類的介面卡模式的介紹,然而介面卡模式還有另外一種形式——物件的介面卡模式,這裡就具體講解下它的實現,實現的分析思路:既然現在介面卡類不能繼承TwoHole抽象類了(因為用繼承就屬於類的介面卡了),但是介面卡類無論如何都要實現客戶端期待的方法的,即Request方法,所以一定是要繼承ThreeHole抽象類或IThreeHole介面的,然而介面卡類的Request方法又必須呼叫TwoHole的SpecificRequest方法,又不能用繼承,這時候就想,不能繼承,但是我們可以在介面卡類中建立TwoHole物件,然後在Requst中使用TwoHole的方法了。正如我們分析的那樣,物件的介面卡模式的實現正式如此。下面就讓我看看具體實現程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
namespace 物件的介面卡模式 { class Client { static void Main(string[] args) { // 現在客戶端可以通過電適配要使用2個孔的插頭了 ThreeHole threehole = new PowerAdapter(); threehole.Request(); Console.ReadLine(); } } /// <summary> /// 三個孔的插頭,也就是介面卡模式中的目標(Target)角色 /// </summary> public class ThreeHole { // 客戶端需要的方法 public virtual void Request() { // 可以把一般實現放在這裡 } } /// <summary> /// 兩個孔的插頭,源角色——需要適配的類 /// </summary> public class TwoHole { public void SpecificRequest() { Console.WriteLine("我是兩個孔的插頭"); } } /// <summary> /// 介面卡類,這裡介面卡類沒有TwoHole類, /// 而是引用了TwoHole物件,所以是物件的介面卡模式的實現 /// </summary> public class PowerAdapter : ThreeHole { // 引用兩個孔插頭的例項,從而將客戶端與TwoHole聯絡起來 public TwoHole twoholeAdaptee = new TwoHole(); /// <summary> /// 實現三個孔插頭介面方法 /// </summary> public override void Request() { twoholeAdaptee.SpecificRequest(); } } } |
從上面程式碼可以看出,物件的介面卡模式正如我們開始分析的思路去實現的, 其中客戶端呼叫程式碼和類的介面卡實現基本相同,下面讓我們看看物件的介面卡模式的類圖,具體類圖如下:
三、介面卡模式的優缺點
在引言部分已經提出,介面卡模式用來解決現有物件與客戶端期待介面不一致的問題,下面詳細總結下介面卡兩種形式的優缺點。
類的介面卡模式:
優點:
- 可以在不修改原有程式碼的基礎上來複用現有類,很好地符合 “開閉原則”
- 可以重新定義Adaptee(被適配的類)的部分行為,因為在類介面卡模式中,Adapter是Adaptee的子類
- 僅僅引入一個物件,並不需要額外的欄位來引用Adaptee例項(這個即是優點也是缺點)。
缺點:
- 用一個具體的Adapter類對Adaptee和Target進行匹配,當如果想要匹配一個類以及所有它的子類時,類的介面卡模式就不能勝任了。因為類的介面卡模式中沒有引入Adaptee的例項,光呼叫this.SpecificRequest方法並不能去呼叫它對應子類的SpecificRequest方法。
- 採用了 “多繼承”的實現方式,帶來了不良的高耦合。
物件的介面卡模式
優點:
- 可以在不修改原有程式碼的基礎上來複用現有類,很好地符合 “開閉原則”(這點是兩種實現方式都具有的)
- 採用 “物件組合”的方式,更符合鬆耦合。
缺點:
- 使得重定義Adaptee的行為較困難,這就需要生成Adaptee的子類並且使得Adapter引用這個子類而不是引用Adaptee本身。
四、使用場景
在以下情況下可以考慮使用介面卡模式:
- 系統需要複用現有類,而該類的介面不符合系統的需求
- 想要建立一個可重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
- 對於物件介面卡模式,在設計裡需要改變多個已有子類的介面,如果使用類的介面卡模式,就要針對每一個子類做一個介面卡,而這不太實際。
五、.NET中介面卡模式的實現
1.介面卡模式在.NET Framework中的一個最大的應用就是COM Interop。COM Interop就好像是COM和.NET之間的一座橋樑(關於COM互操作更多內容可以參考我的互操作系列)。COM元件物件與.NET類物件是完全不同的,但為了使.NET程式
象使用.NET物件一樣使用COM元件,微軟在處理方式上採用了Adapter模式,對COM物件進行包裝,這個包裝類就是RCW(Runtime Callable Wrapper)。RCW實際上是runtime生成的一個.NET類,它包裝了COM元件的方法,並內部實現對COM元件的呼叫。如下圖所示:
2..NET中的另外一個介面卡模式的應用就是DataAdapter。ADO.NET為統一的資料訪問提供了多個介面和基類,其中最重要的介面之一是IdataAdapter。DataAdpter起到了資料庫到DataSet橋接器的作用,使應用程式的資料操作統一到DataSet上,而與具體的資料庫型別無關。甚至可以針對特殊的資料來源編制自己的DataAdpter,從而使我們的應用程式與這些特殊的資料來源相相容。
六、總結
到這裡介面卡模式的介紹就結束了,本文主要介紹了介面卡模式的兩種實現、分析它們的優缺點以及使用場景的介紹,在介面卡模式中,介面卡可以是抽象類,並介面卡模式的實現是非常靈活的,我們完全可以將Adapter模式中的“現存物件”作為新的介面方法引數,介面卡類可以根據引數引數可以返回一個合適的例項給客戶端。
本專題的所有原始碼:設計模式之介面卡模式