.NET设计模式(8):适配器模式(Adapter Pattern)
概述
在軟件系統(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)境所要求的接口?這就是本文要說(shuō)的Adapter 模式。
意圖
將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
結(jié)構(gòu)圖
圖1 類的Adapter模式結(jié)構(gòu)圖
圖2 對(duì)象的Adapter模式結(jié)構(gòu)圖
生活中的例子
適配器模式允許將一個(gè)類的接口轉(zhuǎn)換成客戶期望的另一個(gè)接口,使得原本由于接口不兼容而不能一起工作的類可以一起工作。扳手提供了一個(gè)適配器的例子。一個(gè)孔套在棘齒上,棘齒的每個(gè)邊的尺寸是相同的。在美國(guó)典型的邊長(zhǎng)為1/2''和1/4''。顯然,如果不使用一個(gè)適配器的話,1/2''的棘齒不能適合1/4''的孔。一個(gè)1/2''至1/4''的適配器具有一個(gè)1/2''的陰槽來(lái)套上一個(gè)1/2''的齒,同時(shí)有一個(gè)1/4的陽(yáng)槽來(lái)卡入1/4''的扳手。
圖3使用扳手適配器例子的適配器對(duì)象圖
適配器模式解說(shuō)
我們還是以日志記錄程序?yàn)槔诱f(shuō)明Adapter模式。現(xiàn)在有這樣一個(gè)場(chǎng)景:假設(shè)我們?cè)谲浖_(kāi)發(fā)中要使用一個(gè)第三方的日志記錄工具,該日志記錄工具支持?jǐn)?shù)據(jù)庫(kù)日志記錄DatabaseLog和文本文件記錄FileLog兩種方式,它提供給我們的API接口是Write()方法,使用方法如下:
Log.Write("Logging Message!");
當(dāng)軟件系統(tǒng)開(kāi)發(fā)進(jìn)行到一半時(shí),處于某種原因不能繼續(xù)使用該日志記錄工具了,需要采用另外一個(gè)日志記錄工具,它同樣也支持?jǐn)?shù)據(jù)庫(kù)日志記錄DatabaseLog和文本文件記錄FileLog兩種方式,只不過(guò)它提供給我們的API接口是WriteLog()方法,使用方法如下:
Log.WriteLog("Logging Message!");
該日志記錄工具的類結(jié)構(gòu)圖如下:
圖4日志記錄工具類結(jié)構(gòu)圖
它的實(shí)現(xiàn)代碼如下:
public abstract class LogAdaptee
{
??? public abstract void WriteLog();
}??
public class DatabaseLog:LogAdaptee
{
??? public override void WriteLog()
??? {
??????? Console.WriteLine("Called WriteLog Method");
??? }
}
public class FileLog:LogAdaptee
{
??? public override void WriteLog()
??? {
??????? Console.WriteLine("Called WriteLog Method");
??? }
}
在我們開(kāi)發(fā)完成的應(yīng)用程序中日志記錄接口中(不妨稱之為ILogTarget接口,在本例中為了更加清楚地說(shuō)明,在命名上采用了Adapter模式中的相關(guān)角色名字),卻用到了大量的Write()方法,程序已經(jīng)全部通過(guò)了測(cè)試,我們不能去修改該接口。代碼如下:
public interface ILogTarget
{
??? void Write();
}
這時(shí)也許我們會(huì)想到修改現(xiàn)在的日志記錄工具的API接口,但是由于版權(quán)等原因我們不能夠修改它的源代碼,此時(shí)Adapter模式便可以派上用場(chǎng)了。下面我們通過(guò)Adapter模式來(lái)使得該日志記錄工具能夠符合我們當(dāng)前的需求。
前面說(shuō)過(guò),Adapter模式有兩種實(shí)現(xiàn)形式的實(shí)現(xiàn)結(jié)構(gòu),首先來(lái)看一下類適配器如何實(shí)現(xiàn)。現(xiàn)在唯一可行的辦法就是在程序中引入新的類型,讓它去繼承LogAdaptee類,同時(shí)又實(shí)現(xiàn)已有的ILogTarget接口。由于LogAdaptee有兩種類型的方式,自然我們要引入兩個(gè)分別為DatabaseLogAdapter和FileLogAdapter的類。
圖5 引入類適配器后的結(jié)構(gòu)圖
實(shí)現(xiàn)代碼如下:
public class DatabaseLogAdapter:DatabaseLog,ILogTarget
{
??? public void Write()
??? {
??????? WriteLog();
??? }
}
public class FileLogAdapter:FileLog,ILogTarget
{
??? public void Write()
??? {
??????? this.WriteLog();
??? }
}
這里需要注意的一點(diǎn)是我們?yōu)槊恳环N日志記錄方式都編寫了它的適配類,那為什么不能為抽象類LogAdaptee來(lái)編寫一個(gè)適配類呢?因?yàn)?/span>DatabaseLog和FileLog雖然同時(shí)繼承于抽象類LogAdaptee,但是它們具體的WriteLog()方法的實(shí)現(xiàn)是不同的。只有繼承于該具體類,才能保留其原有的行為。
我們看一下這時(shí)客戶端的程序的調(diào)用方法:
public class App
{
??? public static void Main()
??? {
??????? ILogTarget dbLog = new DatabaseLogAdapter();
??????? dbLog.Write("Logging Database...");
??????? ILogTarget fileLog = new FileLogAdapter();
??????? fileLog.Write("Logging File...");
??? }
}
下面看一下如何通過(guò)對(duì)象適配器的方式來(lái)達(dá)到我們適配的目的。對(duì)象適配器是采用對(duì)象組合而不是使用繼承,類結(jié)構(gòu)圖如下:
圖6引入對(duì)象適配器后的結(jié)構(gòu)圖
實(shí)現(xiàn)代碼如下:
public class LogAdapter:ILogTarget
{
??? private LogAdaptee _adaptee;
??? public LogAdapter(LogAdaptee adaptee)
??? {
??????? this._adaptee = adaptee;???
??? }
??? public void Write()
??? {
??? ??? _adaptee.WriteLog();
??? }
}
與類適配器相比較,可以看到最大的區(qū)別是適配器類的數(shù)量減少了,不再需要為每一種具體的日志記錄方式來(lái)創(chuàng)建一個(gè)適配器類。同時(shí)可以看到,引入對(duì)象適配器后,適配器類不再依賴于具體的DatabaseLog類和FileLog類,更好的實(shí)現(xiàn)了松耦合。
再看一下客戶端程序的調(diào)用方法:
public class App
{
??? public static void Main()
??? {
???????
??????? ILogTarget dbLog = new LogAdapter(new DatabaseLog());
??????? dbLog.Write("Logging Database...");
??????? ILogTarget fileLog = new LogAdapter(new FileLog());
??????? fileLog.Write("Logging Database...");
??? }
}
通過(guò)Adapter模式,我們很好的實(shí)現(xiàn)了對(duì)現(xiàn)有組件的復(fù)用。對(duì)比以上兩種適配方式,可以總結(jié)出,在類適配方式中,我們得到的適配器類DatabaseLogAdapter和FileLogAdapter具有它所繼承的父類的所有的行為,同時(shí)也具有接口ILogTarget的所有行為,這樣其實(shí)是違背了面向?qū)ο笤O(shè)計(jì)原則中的類的單一職責(zé)原則,而對(duì)象適配器則更符合面向?qū)ο蟮木?#xff0c;所以在實(shí)際應(yīng)用中不太推薦類適配這種方式。再換個(gè)角度來(lái)看類適配方式,假設(shè)我們要適配出來(lái)的類在記錄日志時(shí)同時(shí)寫入文件和數(shù)據(jù)庫(kù),那么用對(duì)象適配器我們會(huì)這樣去寫:
public class LogAdapter:ILogTarget
{
??? private LogAdaptee _adaptee1;
??? private LogAdaptee _adaptee2;
??? public LogAdapter(LogAdaptee adaptee1,LogAdaptee adaptee2)
??? {
??????? this._adaptee1 = adaptee1;
??????? this._adaptee2 = adaptee2;
??? }
??? public void Write()
??? {
??????? _adaptee1.WriteLog();
??????? _adaptee2.WriteLog();
??? }
}
如果改用類適配器,難道這樣去寫:
public class DatabaseLogAdapter:DatabaseLog,FileLog,ILogTarget
{
??? public void Write()
??? {
??????? //WriteLog();
??? }
}
顯然是不對(duì)的,這樣的解釋雖說(shuō)有些牽強(qiáng),也足以說(shuō)明一些問(wèn)題,當(dāng)然了并不是說(shuō)類適配器在任何情況下都不使用,針對(duì)開(kāi)發(fā)場(chǎng)景不同,某些時(shí)候還是可以用類適配器的方式。
.NET中的適配器模式
1.Adapter模式在.NET Framework中的一個(gè)最大的應(yīng)用就是COM Interop。COM Interop就好像是COM和.NET之間的一條紐帶,一座橋梁。我們知道,COM組件對(duì)象與.NET類對(duì)象是完全不同的,但為了使COM客戶程序象調(diào)用COM組件一樣調(diào)用.NET對(duì)象,使.NET程序
象使用.NET對(duì)象一樣使用COM組件,微軟在處理方式上采用了Adapter模式,對(duì)COM對(duì)象進(jìn)行包裝,這個(gè)包裝類就是RCW(Runtime Callable Wrapper)。RCW實(shí)際上是runtime生成的一個(gè).NET類,它包裝了COM組件的方法,并內(nèi)部實(shí)現(xiàn)對(duì)COM組件的調(diào)用。如下圖所示:
圖7 .NET程序與COM互相調(diào)用示意圖
2..NET中的另一個(gè)Adapter模式的應(yīng)用就是DataAdapter。ADO.NET為統(tǒng)一的數(shù)據(jù)訪問(wèn)提供了多個(gè)接口和基類,其中最重要的接口之一是IdataAdapter。與之相對(duì)應(yīng)的DataAdpter是一個(gè)抽象類,它是ADO.NET與具體數(shù)據(jù)庫(kù)操作之間的數(shù)據(jù)適配器的基類。DataAdpter起到了數(shù)據(jù)庫(kù)到DataSet橋接器的作用,使應(yīng)用程序的數(shù)據(jù)操作統(tǒng)一到DataSet上,而與具體的數(shù)據(jù)庫(kù)類型無(wú)關(guān)。甚至可以針對(duì)特殊的數(shù)據(jù)源編制自己的DataAdpter,從而使我們的應(yīng)用程序與這些特殊的數(shù)據(jù)源相兼容。注意這是一個(gè)適配器的變體。
實(shí)現(xiàn)要點(diǎn)
1.Adapter模式主要應(yīng)用于“希望復(fù)用一些現(xiàn)存的類,但是接口又與復(fù)用環(huán)境要求不一致的情況”,在遺留代碼復(fù)用、類庫(kù)遷移等方面非常有用。
2.Adapter模式有對(duì)象適配器和類適配器兩種形式的實(shí)現(xiàn)結(jié)構(gòu),但是類適配器采用“多繼承”的實(shí)現(xiàn)方式,帶來(lái)了不良的高耦合,所以一般不推薦使用。對(duì)象適配器采用“對(duì)象組合”的方式,更符合松耦合精神。
3.Adapter模式的實(shí)現(xiàn)可以非常的靈活,不必拘泥于GOF23中定義的兩種結(jié)構(gòu)。例如,完全可以將Adapter模式中的“現(xiàn)存對(duì)象”作為新的接口方法參數(shù),來(lái)達(dá)到適配的目的。
4.Adapter模式本身要求我們盡可能地使用“面向接口的編程”風(fēng)格,這樣才能在后期很方便的適配。[以上幾點(diǎn)引用自MSDN WebCast]
效果
對(duì)于類適配器:
1.用一個(gè)具體的Adapter類對(duì)Adaptee和Taget進(jìn)行匹配。結(jié)果是當(dāng)我們想要匹配一個(gè)類以及所有它的子類時(shí),類Adapter將不能勝任工作。
2.使得Adapter可以重定義Adaptee的部分行為,因?yàn)?/span>Adapter是Adaptee的一個(gè)子類。
3.僅僅引入了一個(gè)對(duì)象,并不需要額外的指針一間接得到Adaptee.
對(duì)于對(duì)象適配器:
1.允許一個(gè)Adapter與多個(gè)Adaptee,即Adaptee本身以及它的所有子類(如果有子類的話)同時(shí)工作。Adapter也可以一次給所有的Adaptee添加功能。
2.使得重定義Adaptee的行為比較困難。這就需要生成Adaptee的子類并且使得Adapter引用這個(gè)子類而不是引用Adaptee本身。
適用性
在以下各種情況下使用適配器模式:
1.系統(tǒng)需要使用現(xiàn)有的類,而此類的接口不符合系統(tǒng)的需要。
2.想要建立一個(gè)可以重復(fù)使用的類,用于與一些彼此之間沒(méi)有太大關(guān)聯(lián)的一些類,包括一些可能在將來(lái)引進(jìn)的類一起工作。這些源類不一定有很復(fù)雜的接口。
3.(對(duì)對(duì)象適配器而言)在設(shè)計(jì)里,需要改變多個(gè)已有子類的接口,如果使用類的適配器模式,就要針對(duì)每一個(gè)子類做一個(gè)適配器,而這不太實(shí)際。
總結(jié)
總之,通過(guò)運(yùn)用Adapter模式,就可以充分享受進(jìn)行類庫(kù)遷移、類庫(kù)重用所帶來(lái)的樂(lè)趣。
參考資料
閻宏,《Java與模式》,電子工業(yè)出版社
James W. Cooper,《C#設(shè)計(jì)模式》,電子工業(yè)出版社
Alan Shalloway James R. Trott,《Design Patterns Explained》,中國(guó)電力出版社
MSDN WebCast 《C#面向?qū)ο笤O(shè)計(jì)模式縱橫談(7):Adapter 適配器模式(結(jié)構(gòu)型模式)》
轉(zhuǎn)載于:https://www.cnblogs.com/Aioria0622/archive/2007/11/21/967505.html
總結(jié)
以上是生活随笔為你收集整理的.NET设计模式(8):适配器模式(Adapter Pattern)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【转载】不要一辈子靠技术生存
- 下一篇: 论坛等级分类