在實際的開發過程中,由于應用環境的變化(例如使用語言的變化),我們需要的實作在新的環境中沒有現存對象可以滿足,但是其他環境卻存在這樣現存的對象。那麼如果将“将現存的對象”在新的環境中進行調用呢?解決這個問題的辦法就是我們本文要介紹的擴充卡模式——使得新環境中不需要去重複實作已經存在了的實作而很好地把現有對象(指原來環境中的現有對象)加入到新環境來使用。
下面讓我們看看擴充卡的定義,擴充卡模式——把一個類的接口變換成用戶端所期待的另一種接口,進而使原本接口不比對而無法一起工作的兩個類能夠在一起工作。擴充卡模式有類的擴充卡模式和對象的擴充卡模式兩種形式,下面我們分别讨論這兩種形式的實作和給出對應的類圖來幫助大家理清類之間的關系。
在這裡以生活中的一個例子來進行示範擴充卡模式的實作,具體場景是: 在生活中,我們買的電器插頭是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
<code>using</code> <code>System;</code>
<code>/// 這裡以插座和插頭的例子來诠釋擴充卡模式</code>
<code>/// 現在我們買的電器插頭是2個孔,但是我們買的插座隻有3個孔的</code>
<code>/// 這是我們想把電器插在插座上的話就需要一個電擴充卡</code>
<code>namespace</code> <code>設計模式之擴充卡模式</code>
<code>{</code>
<code> </code><code>/// <summary></code>
<code> </code><code>/// 用戶端,客戶想要把2個孔的插頭 轉變成三個孔的插頭,這個轉變交給擴充卡就好</code>
<code> </code><code>/// 既然擴充卡需要完成這個功能,是以它必須同時具體2個孔插頭和三個孔插頭的特征</code>
<code> </code><code>/// </summary></code>
<code> </code><code>class</code> <code>Client</code>
<code> </code><code>{</code>
<code> </code><code>static</code> <code>void</code> <code>Main(</code><code>string</code><code>[] args)</code>
<code> </code><code>{</code>
<code> </code><code>// 現在用戶端可以通過電适配要使用2個孔的插頭了</code>
<code> </code><code>IThreeHole threehole = </code><code>new</code> <code>PowerAdapter();</code>
<code> </code><code>threehole.Request();</code>
<code> </code><code>Console.ReadLine();</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>/// 三個孔的插頭,也就是擴充卡模式中的目标角色</code>
<code> </code><code>public</code> <code>interface</code> <code>IThreeHole</code>
<code> </code><code>void</code> <code>Request();</code>
<code> </code><code>/// 兩個孔的插頭,源角色——需要适配的類</code>
<code> </code><code>public</code> <code>abstract</code> <code>class</code> <code>TwoHole</code>
<code> </code><code>public</code> <code>void</code> <code>SpecificRequest()</code>
<code> </code><code>Console.WriteLine(</code><code>"我是兩個孔的插頭"</code><code>);</code>
<code> </code><code>/// 擴充卡類,接口要放在類的後面</code>
<code> </code><code>/// 擴充卡類提供了三個孔插頭的行為,但其本質是調用兩個孔插頭的方法</code>
<code> </code><code>public</code> <code>class</code> <code>PowerAdapter:TwoHole,IThreeHole</code>
<code> </code><code>/// <summary></code>
<code> </code><code>/// 實作三個孔插頭接口方法</code>
<code> </code><code>/// </summary></code>
<code> </code><code>public</code> <code>void</code> <code>Request()</code>
<code> </code><code>// 調用兩個孔插頭方法</code>
<code> </code><code>this</code><code>.SpecificRequest();</code>
<code>}</code>
從上面代碼中可以看出,用戶端希望調用Request方法(即三個孔插頭),但是我們現有的類(即2個孔的插頭)并沒有Request方法,它隻有SpecificRequest方法(即兩個孔插頭本身的方法),然而擴充卡類(擴充卡必須實作三個孔插頭接口和繼承兩個孔插頭類)可以提供這種轉換,它提供了Request方法的實作(其内部調用的是兩個孔插頭,因為擴充卡隻是一個外殼罷了,包裝着兩個孔插頭(因為隻有這樣,電器才能使用),并向外界提供三個孔插頭的外觀,)以供用戶端使用。
上面實作中,因為擴充卡(PowerAdapter類)與源角色(TwoHole類)是繼承關系,是以該擴充卡模式是類的擴充卡模式,具體對應的類圖為:

上面都是類的擴充卡模式的介紹,然而擴充卡模式還有另外一種形式——對象的擴充卡模式,這裡就具體講解下它的實作,實作的分析思路:既然現在擴充卡類不能繼承TwoHole抽象類了(因為用繼承就屬于類的擴充卡了),但是擴充卡類無論如何都要實作用戶端期待的方法的,即Request方法,是以一定是要繼承ThreeHole抽象類或IThreeHole接口的,然而擴充卡類的Request方法又必須調用TwoHole的SpecificRequest方法,又不能用繼承,這時候就想,不能繼承,但是我們可以在擴充卡類中建立TwoHole對象,然後在Requst中使用TwoHole的方法了。正如我們分析的那樣,對象的擴充卡模式的實作正式如此。下面就讓我看看具體實作代碼:
<code>namespace</code> <code>對象的擴充卡模式</code>
<code> </code><code>ThreeHole threehole = </code><code>new</code> <code>PowerAdapter();</code>
<code> </code><code>/// 三個孔的插頭,也就是擴充卡模式中的目标(Target)角色</code>
<code> </code><code>public</code> <code>class</code> <code>ThreeHole</code>
<code> </code><code>// 用戶端需要的方法</code>
<code> </code><code>public</code> <code>virtual</code> <code>void</code> <code>Request()</code>
<code> </code><code>// 可以把一般實作放在這裡</code>
<code> </code><code>public</code> <code>class</code> <code>TwoHole</code>
<code> </code><code>/// 擴充卡類,這裡擴充卡類沒有TwoHole類,</code>
<code> </code><code>/// 而是引用了TwoHole對象,是以是對象的擴充卡模式的實作</code>
<code> </code><code>public</code> <code>class</code> <code>PowerAdapter : ThreeHole</code>
<code> </code><code>// 引用兩個孔插頭的執行個體,進而将用戶端與TwoHole聯系起來</code>
<code> </code><code>public</code> <code>TwoHole twoholeAdaptee = </code><code>new</code> <code>TwoHole();</code>
<code> </code><code>public</code> <code>override</code> <code>void</code> <code>Request()</code>
<code> </code><code>twoholeAdaptee.SpecificRequest();</code>
從上面代碼可以看出,對象的擴充卡模式正如我們開始分析的思路去實作的, 其中用戶端調用代碼和類的擴充卡實作基本相同,下面讓我們看看對象的擴充卡模式的類圖,具體類圖如下:
在引言部分已經提出,擴充卡模式用來解決現有對象與用戶端期待接口不一緻的問題,下面詳細總結下擴充卡兩種形式的優缺點。
類的擴充卡模式:
優點:
可以在不修改原有代碼的基礎上來複用現有類,很好地符合 “開閉原則”
可以重新定義Adaptee(被适配的類)的部分行為,因為在類擴充卡模式中,Adapter是Adaptee的子類
僅僅引入一個對象,并不需要額外的字段來引用Adaptee執行個體(這個即是優點也是缺點)。
缺點:
用一個具體的Adapter類對Adaptee和Target進行比對,當如果想要比對一個類以及所有它的子類時,類的擴充卡模式就不能勝任了。因為類的擴充卡模式中沒有引入Adaptee的執行個體,光調用this.SpecificRequest方法并不能去調用它對應子類的SpecificRequest方法。
采用了 “多繼承”的實作方式,帶來了不良的高耦合。
對象的擴充卡模式
可以在不修改原有代碼的基礎上來複用現有類,很好地符合 “開閉原則”(這點是兩種實作方式都具有的)
采用 “對象組合”的方式,更符合松耦合。
使得重定義Adaptee的行為較困難,這就需要生成Adaptee的子類并且使得Adapter引用這個子類而不是引用Adaptee本身。
在以下情況下可以考慮使用擴充卡模式:
系統需要複用現有類,而該類的接口不符合系統的需求
想要建立一個可重複使用的類,用于與一些彼此之間沒有太大關聯的一些類,包括一些可能在将來引進的類一起工作。
對于對象擴充卡模式,在設計裡需要改變多個已有子類的接口,如果使用類的擴充卡模式,就要針對每一個子類做一個擴充卡,而這不太實際。
象使用.NET對象一樣使用COM元件,微軟在處理方式上采用了Adapter模式,對COM對象進行包裝,這個包裝類就是RCW(Runtime Callable Wrapper)。RCW實際上是runtime生成的一個.NET類,它包裝了COM元件的方法,并内部實作對COM元件的調用。如下圖所示:
2..NET中的另外一個擴充卡模式的應用就是DataAdapter。ADO.NET為統一的資料通路提供了多個接口和基類,其中最重要的接口之一是IdataAdapter。DataAdpter起到了資料庫到DataSet橋接器的作用,使應用程式的資料操作統一到DataSet上,而與具體的資料庫類型無關。甚至可以針對特殊的資料源編制自己的DataAdpter,進而使我們的應用程式與這些特殊的資料源相相容。
到這裡擴充卡模式的介紹就結束了,本文主要介紹了擴充卡模式的兩種實作、分析它們的優缺點以及使用場景的介紹,在擴充卡模式中,擴充卡可以是抽象類,并擴充卡模式的實作是非常靈活的,我們完全可以将Adapter模式中的“現存對象”作為新的接口方法參數,擴充卡類可以根據參數參數可以傳回一個合适的執行個體給用戶端。
<a href="http://down.51cto.com/data/2363601" target="_blank">附件:http://down.51cto.com/data/2363601</a>
本文轉自LearningHard 51CTO部落格,原文連結:http://blog.51cto.com/learninghard/1308323,如需轉載請自行聯系原作者