?? 從今天開(kāi)始我們開(kāi)始講【結(jié)構(gòu)型】設(shè)計(jì)模式,【結(jié)構(gòu)型】設(shè)計(jì)模式有如下幾種:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式?!緞?chuàng)建型】的設(shè)計(jì)模式解決的是對(duì)象創(chuàng)建的問(wèn)題,那【結(jié)構(gòu)型】設(shè)計(jì)模式解決的是類(lèi)和對(duì)象的組合關(guān)系的問(wèn)題。今天我們就開(kāi)始講【結(jié)構(gòu)型】設(shè)計(jì)模式里面的第一個(gè)設(shè)計(jì)模式,中文名稱:適配器模式,英文名稱:Adapter Pattern。說(shuō)起這個(gè)模式其實(shí)很簡(jiǎn)單,在現(xiàn)實(shí)生活中也有很多實(shí)例,比如:我們手機(jī)的充電器,充電器的接頭,有的是把兩相電轉(zhuǎn)換為三相電的,當(dāng)然也有把三相電轉(zhuǎn)換成兩相電的。我們經(jīng)常使用筆記本電腦,筆記本電腦的工作電壓和我們家里照明電壓是不一致的,當(dāng)然也就需要充電器把照明電壓轉(zhuǎn)換成筆記本的工作電壓,只有這樣筆記本電腦才可以正常工作。太多了,就不一一列舉了。我們只要記住一點(diǎn),適配就是轉(zhuǎn)換,把不能在一起工作的兩樣?xùn)|西通過(guò)轉(zhuǎn)換,讓他們可以在一起工作。
二、適配器模式的詳細(xì)介紹
2.1、動(dòng)機(jī)(Motivate)
?? 在軟件系統(tǒng)中,由于應(yīng)用環(huán)境的變化,常常需要將“一些現(xiàn)存的對(duì)象”放在新的環(huán)境中應(yīng)用,但是新環(huán)境要求的接口是這些現(xiàn)存對(duì)象所不滿足的。如何應(yīng)對(duì)這種“遷移的變化”?如何既能利用現(xiàn)有對(duì)象的良好實(shí)現(xiàn),同時(shí)又能滿足新的應(yīng)用環(huán)境所要求的接口?
2.2、意圖(Intent)
?? 將一個(gè)類(lèi)的接口轉(zhuǎn)換成客戶希望的另一個(gè)接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類(lèi)可以一起工作。?????????????????????????????????????????????????????????????????? --《設(shè)計(jì)模式》Gof
2.3、結(jié)構(gòu)圖(Structure)
?? 適配器有兩種結(jié)構(gòu)
1】、-對(duì)象適配器(更常用)
? ?? ???
1 namespace 對(duì)象的適配器模式 2 { 3 ///<summary> 4 ///家里只有兩個(gè)孔的插座,也懶得買(mǎi)插線板了,還要花錢(qián),但是我的手機(jī)是一個(gè)有3個(gè)小柱子的插頭,明顯直接搞不定,那就適配吧 5 ///</summary> 6 class Client 7 { 8 static void Main(string[] args) 9 {10 //好了,現(xiàn)在就可以給手機(jī)充電了11 TwoHoleTarget homeTwoHole = new ThreeToTwoAdapter();12 homeTwoHole.Request();13 Console.ReadLine();14 }15 }16 17 /// <summary>18 /// 我家只有2個(gè)孔的插座,也就是適配器模式中的目標(biāo)(Target)角色,這里可以寫(xiě)成抽象類(lèi)或者接口19 /// </summary>20 public class TwoHoleTarget21 {22 // 客戶端需要的方法23 public virtual void Request()24 {25 Console.WriteLine("兩孔的充電器可以使用");26 }27 }28 29 /// <summary>30 /// 手機(jī)充電器是有3個(gè)柱子的插頭,源角色——需要適配的類(lèi)(Adaptee)31 /// </summary>32 public class ThreeHoleAdaptee33 {34 public void SpecificRequest()35 {36 Console.WriteLine("我是3個(gè)孔的插頭也可以使用了");37 }38 }39 40 /// <summary>41 /// 適配器類(lèi),TwoHole這個(gè)對(duì)象寫(xiě)成接口或者抽象類(lèi)更好,面向接口編程嘛42 /// </summary>43 public class ThreeToTwoAdapter : TwoHoleTarget44 {45 // 引用兩個(gè)孔插頭的實(shí)例,從而將客戶端與TwoHole聯(lián)系起來(lái)46 private ThreeHoleAdaptee threeHoleAdaptee = new ThreeHoleAdaptee();47 //這里可以繼續(xù)增加適配的對(duì)象。。48 49 /// <summary>50 /// 實(shí)現(xiàn)2個(gè)孔插頭接口方法51 /// </summary>52 public override void Request()53 {54 //可以做具體的轉(zhuǎn)換工作55 threeHoleAdaptee.SpecificRequest();56 //可以做具體的轉(zhuǎn)換工作57 }58 }59 }
?? 2、類(lèi)的適配器模式實(shí)現(xiàn)
1 namespace 設(shè)計(jì)模式之適配器模式 2 { 3 /// <summary> 4 /// 這里手機(jī)充電器為例,我們的家的插座是兩相電的,但是手機(jī)的插座接頭是三相電的 5 /// </summary> 6 class Client 7 { 8 static void Main(string[] args) 9 {10 //好了,現(xiàn)在可以充電了11 ITwoHoleTarget change = new ThreeToTwoAdapter();12 change.Request();13 Console.ReadLine();14 }15 }16 17 /// <summary>18 /// 我家只有2個(gè)孔的插座,也就是適配器模式中的目標(biāo)角色(Target),這里只能是接口,也是類(lèi)適配器的限制19 /// </summary>20 public interface ITwoHoleTarget21 {22 void Request();23 }24 25 /// <summary>26 /// 3個(gè)孔的插頭,源角色——需要適配的類(lèi)(Adaptee)27 /// </summary>28 public abstract class ThreeHoleAdaptee29 {30 public void SpecificRequest()31 {32 Console.WriteLine("我是三個(gè)孔的插頭");33 }34 }35 36 /// <summary>37 /// 適配器類(lèi),接口要放在類(lèi)的后面,在此無(wú)法適配更多的對(duì)象,這是類(lèi)適配器的不足38 /// </summary>39 public class ThreeToTwoAdapter:ThreeHoleAdaptee,ITwoHoleTarget40 {41 /// <summary>42 /// 實(shí)現(xiàn)2個(gè)孔插頭接口方法43 /// </summary>44 public void Request()45 {46 // 調(diào)用3個(gè)孔插頭方法47 this.SpecificRequest();48 }49 }50 }
?? 代碼都很簡(jiǎn)答,誰(shuí)都可以看得懂,也有詳細(xì)的備注。
三、適配器模式的實(shí)現(xiàn)要點(diǎn):
?? ?
????? 1、Adapter模式主要應(yīng)用于“希望復(fù)用一些現(xiàn)存的類(lèi),但是接口又與復(fù)用環(huán)境要求不一致的情況”,在遺留代碼復(fù)用、類(lèi)庫(kù)遷移等方面非常有用。
2、GoF23定義了兩種Adapter模式的實(shí)現(xiàn)結(jié)構(gòu):對(duì)象適配器和類(lèi)適配器。類(lèi)適配器采用“多繼承”的實(shí)現(xiàn)方式,在C#語(yǔ)言中,如果被適配角色是類(lèi),Target的實(shí)現(xiàn)只能是接口,因?yàn)镃#語(yǔ)言只支持接口的多繼承的特性。在C#語(yǔ)言中類(lèi)適配器也很難支持適配多個(gè)對(duì)象的情況,同時(shí)也會(huì)帶來(lái)了不良的高耦合和違反類(lèi)的職責(zé)單一的原則,所以一般不推薦使用。對(duì)象適配器采用“對(duì)象組合”的方式,更符合松耦合精神,對(duì)適配的對(duì)象也沒(méi)限制,可以一個(gè),也可以多個(gè),但是,使得重定義Adaptee的行為較困難,這就需要生成Adaptee的子類(lèi)并且使得Adapter引用這個(gè)子類(lèi)而不是引用Adaptee本身。Adapter模式可以實(shí)現(xiàn)的非常靈活,不必拘泥于GoF23中定義的兩種結(jié)構(gòu)。例如,完全可以將Adapter模式中的“現(xiàn)存對(duì)象”作為新的接口方法參數(shù),來(lái)達(dá)到適配的目的。
3、Adapter模式本身要求我們盡可能地使用“面向接口的編程”風(fēng)格,這樣才能在后期很方便地適配。
????? 適配器模式用來(lái)解決現(xiàn)有對(duì)象與客戶端期待接口不一致的問(wèn)題,下面詳細(xì)總結(jié)下適配器兩種形式的優(yōu)缺點(diǎn)。
???? 1】、類(lèi)的適配器模式:
???????? 優(yōu)點(diǎn):
?????????????? (1)、可以在不修改原有代碼的基礎(chǔ)上來(lái)復(fù)用現(xiàn)有類(lèi),很好地符合 “開(kāi)閉原則”
?????????????? (2)、可以重新定義Adaptee(被適配的類(lèi))的部分行為,因?yàn)樵陬?lèi)適配器模式中,Adapter是Adaptee的子類(lèi)
?????????????? (3)、僅僅引入一個(gè)對(duì)象,并不需要額外的字段來(lái)引用Adaptee實(shí)例(這個(gè)即是優(yōu)點(diǎn)也是缺點(diǎn))。
???????? 缺點(diǎn):
?????????????? (1)、用一個(gè)具體的Adapter類(lèi)對(duì)Adaptee和Target進(jìn)行匹配,當(dāng)如果想要匹配一個(gè)類(lèi)以及所有它的子類(lèi)時(shí),類(lèi)的適配器模式就不能勝任了。因?yàn)轭?lèi)的適配器模式中沒(méi)有引入Adaptee的實(shí)例,光調(diào)用this.SpecificRequest方法并不能去調(diào)用它對(duì)應(yīng)子類(lèi)的SpecificRequest方法。
?????????????? (2)、采用了 “多繼承”的實(shí)現(xiàn)方式,帶來(lái)了不良的高耦合。
??????? 2】、對(duì)象的適配器模式
???????????? 優(yōu)點(diǎn):
????????????????? (1)、可以在不修改原有代碼的基礎(chǔ)上來(lái)復(fù)用現(xiàn)有類(lèi),很好地符合 “開(kāi)閉原則”(這點(diǎn)是兩種實(shí)現(xiàn)方式都具有的)
????????????????? (2)、采用 “對(duì)象組合”的方式,更符合松耦合。
??????????? 缺點(diǎn):
????????????????? (1)、使得重定義Adaptee的行為較困難,這就需要生成Adaptee的子類(lèi)并且使得Adapter引用這個(gè)子類(lèi)而不是引用Adaptee本身。
???? 3】、適配器模式使用的場(chǎng)景:
????????????? (1)、系統(tǒng)需要復(fù)用現(xiàn)有類(lèi),而該類(lèi)的接口不符合系統(tǒng)的需求
????????????? (2)、想要建立一個(gè)可重復(fù)使用的類(lèi),用于與一些彼此之間沒(méi)有太大關(guān)聯(lián)的一些類(lèi),包括一些可能在將來(lái)引進(jìn)的類(lèi)一起工作。
????????????? (3)、對(duì)于對(duì)象適配器模式,在設(shè)計(jì)里需要改變多個(gè)已有子類(lèi)的接口,如果使用類(lèi)的適配器模式,就要針對(duì)每一個(gè)子類(lèi)做一個(gè)適配器,而這不太實(shí)際。
四、.NET 中適配器模式的實(shí)現(xiàn)
??? 說(shuō)道適配器模式在Net中的實(shí)現(xiàn)就很多了,比如:System.IO里面的很多類(lèi)都有適配器的影子,當(dāng)我們操作文件的時(shí)候,其實(shí)里面調(diào)用了COM的接口實(shí)現(xiàn)。以下兩點(diǎn)也是適配器使用的案例:
????? 1.在.NET中復(fù)用COM對(duì)象:
? COM對(duì)象不符合.NET對(duì)象的接口,使用tlbimp.exe來(lái)創(chuàng)建一個(gè)Runtime Callable Wrapper(RCW)以使其符合.NET對(duì)象的接口,COM Interop就好像是COM和.NET之間的一座橋梁。
2..NET數(shù)據(jù)訪問(wèn)類(lèi)(Adapter變體):
各種數(shù)據(jù)庫(kù)并沒(méi)有提供DataSet接口,使用DbDataAdapter可以將任何個(gè)數(shù)據(jù)庫(kù)訪問(wèn)/存取適配到一個(gè)DataSet對(duì)象上,DbDataAdapter在數(shù)據(jù)庫(kù)和DataSet之間做了很好的適配。當(dāng)然還有SqlDataAdapter類(lèi)型了,針對(duì)微軟SqlServer類(lèi)型的數(shù)據(jù)庫(kù)在和DataSet之間進(jìn)行適配。
聯(lián)系客服