适配器模式(Adapter)和外观模式(Facade)
適配器模式(Adapter)
還是先從引入說(shuō)起,先來(lái)看一個(gè)問(wèn)題吧,總所周知,在中國(guó)通用的電壓時(shí) 220V,
而美國(guó)電壓則是 110V,如果有經(jīng)常在美國(guó)和中國(guó)之間跑的 IT 人,而其筆記本都是隨身攜帶的,
那么它的筆記本的電壓?jiǎn)栴}如何解決呢?
(因?yàn)樵诿绹?guó)和中國(guó)電壓不同,所以一般的電器會(huì)不通用的)
而適配器在這個(gè)問(wèn)題上體現(xiàn)得妙極妙極。
現(xiàn)在的筆記本都有一個(gè)電源適配器,而正是這個(gè)電源適配器來(lái)解決上面提到的適配器問(wèn)題,
比如,一款索尼筆記本,其輸入電流為交流100V~240V,而輸出則是統(tǒng)一的直流 19.5V,
在電源適配器的一端輸入交流電流,然后通過(guò)電源適配器把電源變成需要的電壓,
也就是適配器的作用是使得一個(gè)東西適合另外一個(gè)東西。
?????????????
??????????????
下面來(lái)給出適配器模式的定義
適配器模式將一個(gè)接口轉(zhuǎn)換成另外一個(gè)接口,以符合客戶的期望。
主要用于以下情況:
比如現(xiàn)在我有一個(gè)舊的軟件系統(tǒng),而其中有一個(gè)組件呢,它已經(jīng)過(guò)時(shí)了,需要更新,
所以我又有了第三方的組件(新組件),但是舊組件的接口和新組件的接口不同,
同時(shí),您又不想去改變現(xiàn)有的代碼(如果系統(tǒng)大的話,或許您改都改不了),
此時(shí)呢,就是讓適配器模式登場(chǎng)的時(shí)刻了,
您可以通過(guò)適配器模式將新組件的一些接口轉(zhuǎn)換成為你所期望的接口(也就是和新組件符合),
這樣的話,您就無(wú)需要改變?cè)瓉?lái)的代碼而輕松實(shí)現(xiàn)從舊組件更新到新組件了。
然后呢,還有一種比較好的應(yīng)用就是,比如現(xiàn)在很多在 Windows 上的東西都不能在 Linux 上運(yùn)行,
比如一個(gè) WINE 的工具,它呢,就允許用戶在 Linux 環(huán)境下運(yùn)行 Windows 程序,這也是一種適配器。
其實(shí)呢,可以這樣來(lái)理解適配器模式的,適配器模式就是將一些對(duì)象包裝起來(lái),
然后讓它們的接口看起來(lái)是別的接口。
還有需要提及的是:
適配器模式本來(lái)是分為了類適配器模式和對(duì)象適配器模式,但是由于類適配器模式要以多重繼承為前提,
而 C# 呢不支持多重繼承,
所以在這里只介紹對(duì)象適配器,如果有對(duì)類適配器模式感興趣的話,可以使用 C++ 來(lái)實(shí)現(xiàn)一下。
????????????
???????????
下面給出對(duì)象適配器的結(jié)構(gòu)圖
??????????
?????????????
下面就來(lái)看一個(gè)簡(jiǎn)單的 Demo 來(lái)說(shuō)明適配器的具體作用
Demo 大概情況是這樣的,我需要使用一個(gè)新的組件來(lái)替換掉我系統(tǒng)中已經(jīng)過(guò)時(shí)了的組件,
所以我使用了一個(gè)第三方組件,
而這個(gè)組件當(dāng)中的接口的名字居然都是用中文寫(xiě)的(這個(gè)可能是開(kāi)發(fā)這個(gè)組件的程序員的問(wèn)題所造成的),
而我現(xiàn)有的系統(tǒng)中的的舊有的組件的接口中確是通過(guò)英文來(lái)調(diào)用的組件,而由于系統(tǒng)過(guò)于復(fù)雜,
所以難以更改,所以我便選擇了使用適配器模式來(lái)解決這個(gè)問(wèn)題:
看一下類圖吧
先來(lái)看 Target 類(Target 類代表能夠被客戶端使用的接口)
namespace Adapter
{
??? public abstract class Target
??? {
??????? //溫度
??????? /// <summary>
??????? /// 下面的接口才是可以被客戶端所識(shí)別的接口,也就是目標(biāo)接口
??????? /// 而前面在被適配器類中的中文卻不能被客戶端識(shí)別,需要被適配
??????? /// </summary>
??????? public abstract void GetTemperature();
??????? //氣壓
??????? public abstract void GetPressure();
??????? //濕度
??????? public abstract void GetHumidity();
??????? //紫外線強(qiáng)度
??????? public abstract void GetUltraviolet();
??? }
}
再來(lái)看需要被適配的類 Adaptee(Adaptee 中的接口由于不能被客戶端識(shí)別,所以需要被適配)
using System;
namespace Adapter
{
??? class Adaptee
??? {
??????? /// <summary>
??????? /// 在被適配器類中的接口并不是客戶端需要的接口
??????? /// 比如這里是使用的中文,而我在客戶端卻必須要使用英文
??????? /// 所以在這里我必須使用適配器來(lái)適配
??????? /// </summary>
??????? public void 得到溫度()
??????? {
??????????? Console.WriteLine("您得到了今日的溫度");
??????? }
??????? public void 得到氣壓()
??????? {
??????????? Console.WriteLine("您得到了今日的氣壓");
??????? }
??????? public void 得到濕度()
??????? {
??????????? Console.WriteLine("您得到了今日的濕度");
??????? }
??????? public void 得到紫外線強(qiáng)度()
??????? {
??????????? Console.WriteLine("您得到了今日的紫外線強(qiáng)度");
??????? }
??? }
}
然后就要看適配器中的代碼部分了(適配器將不能被客戶端識(shí)別的接口間接轉(zhuǎn)換為可以被識(shí)別的接口)
namespace Adapter
{
??? public class Adapter:Target
??? {
??????? //在適配器中必須要維護(hù)一個(gè)被適配器類的對(duì)象
??????? private Adaptee adaptee = new Adaptee();
???? ?? /// <summary>
??????? /// 通過(guò)適配器來(lái)適配原來(lái)不能被客戶端所認(rèn)識(shí)的接口
??????? /// </summary>
??????? public override void GetTemperature()
??????? {
??????????? adaptee.得到溫度();
??????? }
??????? public override void GetPressure()
??????? {
??????????? adaptee.得到氣壓();
??????? }
??????? public override void GetHumidity()
??????? {
??????????? adaptee.得到濕度();
??????? }
??????? public override void GetUltraviolet()
??????? {
??????????? adaptee.得到紫外線強(qiáng)度();
??????? }
??? }
}
最后再來(lái)看客戶端就 OK 了
using System;
namespace AdapterTest
{
??? class Program
??? {
??????? static void Main(string[] args)
??????? {
???????? ?? //實(shí)例化一個(gè)適配器給目標(biāo)接口
??????????? Adapter.Target target = new Adapter.Adapter();
??????????? //下面的這些就是客戶端可以被識(shí)別了接口了
??????????? target.GetTemperature();
??????????? target.GetPressure();
??????????? target.GetHumidity();
??????????? target.GetUltraviolet();
??????????? Console.ReadKey();
??????? }
??? }
}
效果如下
????????
????????????
可以看出,上面的適配器就是一個(gè)中介人,它把本來(lái)客戶端的請(qǐng)求轉(zhuǎn)換成了 Adaptee 所代表的接口所能理解的請(qǐng)求。
或者說(shuō)是,把本來(lái)客戶端不認(rèn)識(shí)的 Adaptee 間接介紹給了客戶端認(rèn)識(shí),不過(guò)注意是間接。
下面還來(lái)總結(jié)一下客戶端使用適配器的過(guò)程:
首先是:客戶通過(guò)目標(biāo)接口調(diào)用適配器的方法,并且對(duì)適配器發(fā)出請(qǐng)求。
然后呢:適配器使用被適配器接口把請(qǐng)求轉(zhuǎn)換成了被適配器者的一個(gè)或多個(gè)調(diào)用接口。
最后呢:客戶端接收到調(diào)用的結(jié)果,但是客戶端并不會(huì)知道請(qǐng)求的處理者是被適配者(客戶端和被適配者是完全解耦的),
???????????? 其也不知道適配器在這中間起到的作用。
???????
同時(shí)也可以總結(jié)一下適配器模式的適用性:
如果你想使用一個(gè)已經(jīng)存在了的接口,而這個(gè)接口卻不符合你的需求,此時(shí)就可以考慮使用適配器模式。
適配器模式在 . NET 中的應(yīng)用
這里要介紹的就是 DataAdapter
使用過(guò) ADO.NET 等數(shù)據(jù)訪問(wèn)之類的操作的話,應(yīng)該都使用過(guò) DataAdapter
DataAdapter 的主要作用是用來(lái)在 DataSet 和數(shù)據(jù)源之間提供一個(gè)適配器功能來(lái)實(shí)現(xiàn)檢索和保存數(shù)據(jù)。
因?yàn)閿?shù)據(jù)源有可能是 DB2 啊,SqlServer 啊,Oracle 啊等等,而這些數(shù)據(jù)庫(kù)的數(shù)據(jù)在組織上都有一定的差別,
所以,它們的接口單單對(duì)于 DataSet 來(lái)說(shuō)的話,是有區(qū)別的,而且區(qū)別還比較大,
而我們總不可能針對(duì)每一種數(shù)據(jù)庫(kù)都使用不同的 DataSet 來(lái)保存和檢索數(shù)據(jù)吧,我們希望的是提供一種統(tǒng)一的 DataSet ,
而其既可以對(duì) Oracle 使用,又可以對(duì) DB2 等等數(shù)據(jù)庫(kù)使用,也就是一種通用的 DataSet 類型。
所以在這里面便可以使用適配器模式了,
我們?cè)跀?shù)據(jù)源和 DataSet 之間插入一個(gè)適配器 DataAdapter ,通過(guò)適配器來(lái)實(shí)現(xiàn)對(duì)各種數(shù)據(jù)庫(kù)的不同應(yīng)用(提供給客戶端統(tǒng)一接口),
而在 DataSet 中,其只能看到 DataAdapter 這一層,對(duì)于數(shù)據(jù)源的話,它是不需要過(guò)問(wèn)的,
這樣也就是實(shí)現(xiàn)了一種通用的 DataSet。
?????????????
????????????
?????????????
外觀模式(Facade)
還是從《Head First Design Patterns》中的例子說(shuō)起(我重新整理了那個(gè)例子),
例子是這樣描述的,說(shuō)是美國(guó)有很多人搞家庭影院(我考慮一種最簡(jiǎn)單的方式,也就是全部是打開(kāi)和關(guān)閉),
在家庭影院中,首先必須要有燈光,屏幕,投影機(jī),功放機(jī),DVD 播放器這幾個(gè)基本的工具,
而燈光呢可以關(guān)閉燈光,打開(kāi)燈光,
投影機(jī)呢,可以打開(kāi)和關(guān)閉投影機(jī),
屏幕呢,也可以打開(kāi)和關(guān)閉,
功放機(jī)的話,關(guān)閉音量,打開(kāi)音量,
DVD 播放器的話可以打開(kāi)播放器和關(guān)閉播放器。
以最普通的方式來(lái)實(shí)現(xiàn)觀看電影的話,估計(jì)類圖會(huì)如下所示:
然后我要打開(kāi)看電影的話,我必須在客戶端執(zhí)行下面的操作,
先打開(kāi)投影儀,再打開(kāi)功放機(jī),再打開(kāi)屏幕,再打開(kāi) DVD 播放機(jī),再打開(kāi)燈光,
在經(jīng)歷了這么多操作后,您才可以看一場(chǎng)電影(看得多不爽啊,居然這么多操作,太復(fù)雜了),
而后在關(guān)閉的時(shí)候,你還是要先關(guān)閉投影儀,再關(guān)閉功放機(jī),再關(guān)閉屏幕,再關(guān)閉 DVD 播放機(jī),再關(guān)閉燈光,
哦,這是太復(fù)雜了!!!
在客戶端居然有那么多操作(問(wèn)題是還有一些用戶可能不知道如何使用其中的一個(gè)工具那他便看不了電影),
用戶簡(jiǎn)直會(huì)煩死去!!!
上面其實(shí)反映的是一個(gè)現(xiàn)今軟件開(kāi)發(fā)系統(tǒng)中的一個(gè)比較常見(jiàn)的現(xiàn)象,
那就是客戶端程序經(jīng)常和復(fù)雜系統(tǒng)的內(nèi)部子系統(tǒng)產(chǎn)生直接聯(lián)系,而導(dǎo)致客戶程序隨著子系統(tǒng)的變化而變化。
而上面的例子中呢,客戶端程序便是用戶的操作,而復(fù)雜系統(tǒng)的內(nèi)部子系統(tǒng)代表的就是這些工具的一些使用接口。
上面的例子中我還只是使用了最簡(jiǎn)單的工具操作接口,即簡(jiǎn)單的打開(kāi)和關(guān)閉,
如果在子系統(tǒng),即各個(gè)工具中還有新的功能的話呢?
那么必然會(huì)導(dǎo)致客戶端代碼得變化。
要想解決上面的這一串問(wèn)題,
你必須要簡(jiǎn)化客戶程序與子系統(tǒng)之間的交互接口(要使得不會(huì)使用所有工具的用戶也可以實(shí)現(xiàn)觀看電影),
然后就是要解除客戶程序和子系統(tǒng)之間的耦合,而外觀模式正好可以解決這個(gè)問(wèn)題。
???????
?????????
???????????
外觀模式(Facade)的定義
為子系統(tǒng)中的一組接口提供一個(gè)一致的界面,用來(lái)訪問(wèn)子系統(tǒng)中的一群接口,
此模式定義了一個(gè)高層的接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。
簡(jiǎn)單的說(shuō),就是外觀模式將一個(gè)或者多個(gè)類的復(fù)雜的操作進(jìn)行了隱藏,只顯示出一個(gè)一致的界面供客戶端使用。
還有需要注意的是,外觀模式僅僅是給你提供了更為直接和容易的操作方式,它并沒(méi)有把原來(lái)的子系統(tǒng)進(jìn)行隔離,
所以,如果你還需要子系統(tǒng)類的更高層的功能,還是可以使用原來(lái)的子系統(tǒng)的,這個(gè)是外觀模式的一大優(yōu)點(diǎn)。
同時(shí),通過(guò)外觀模式可以子系統(tǒng)的多個(gè)接口上建立一個(gè)高層接口,并且將這個(gè)高層接口提供給客戶端使用,
這樣便可以解除掉客戶端和復(fù)雜子系統(tǒng)之間的耦合。
同時(shí),外觀模式也可以使我們遵循迪米特法則(也就是最小知識(shí)原則)。
???????????????????
????????????????
?????????????????
外觀模式結(jié)構(gòu)圖
從上面的類圖就可以看出了,確實(shí)通過(guò)外觀模式可以實(shí)現(xiàn)提供簡(jiǎn)單的接口(OpenMovie 和 CloseMovie)給客戶端,
也給客戶端和子系統(tǒng)之間實(shí)現(xiàn)了解耦。
??
????
下面就通過(guò)代碼來(lái)實(shí)現(xiàn)上面的這個(gè) Demo
(這個(gè) Demo 為了演示,在 Projector 即投影儀類中添加了兩個(gè)方法,即設(shè)置為寬屏模式播放,或者是標(biāo)準(zhǔn)模式播放)
在這里還是貼出一下類圖吧
下面就先來(lái)看幾個(gè)播放工具的代碼吧
using System;
namespace Facade
{
??? /// <summary>
??? /// 投影儀
??? /// </summary>
??? public class Projector
??? {
??????? public void OpenProjector()
??????? {
??????????? Console.WriteLine("打開(kāi)投影儀");
??????? }
??????? public void CloseProjector()
??????? {
??????????? Console.WriteLine("關(guān)閉投影儀");
??????? }
??????? public void SetWideScreen()
??????? {
??????????? Console.WriteLine("投影儀狀態(tài)為寬屏模式");
??????? }
??????? public void SetStandardScreen()
??????? {
??????????? Console.WriteLine("投影儀狀態(tài)為標(biāo)準(zhǔn)模式");
??????? }
??? }
}
????????
?????????
using System;
namespace Facade
{
??? /// <summary>
??? /// 功放機(jī)
??? /// </summary>
??? public class Amplifier
??? {
??????? public void OpenAmplifier()
??????? {
??????????? Console.WriteLine("打開(kāi)功放機(jī)");
??????? }
??????? public void CloseAmplifier()
??????? {
??????????? Console.WriteLine("關(guān)閉功放機(jī)");
??????? }
??? }
}
?????????????
???????????????
using System;
namespace Facade
{
??? /// <summary>
??? /// 屏幕
??? /// </summary>
??? public class Screen
??? {
??????? public void OpenScreen()
??????? {
??????????? Console.WriteLine("打開(kāi)屏幕");
??????? }
??????? public void CloseScreen()
??????? {
??????????? Console.WriteLine("關(guān)閉屏幕");
??????? }
??? }
}
???????????????
???????????????
using System;
namespace Facade
{
??? /// <summary>
??? /// DVD播放器
??? /// </summary>
??? public class DVDPlayer
??? {
??????? public void OpenDVDPlayer()
??????? {
??????????? Console.WriteLine("打開(kāi) DVD 播放器");
??????? }
??????? public void CloseDVDPlayer()
??????? {
??????????? Console.WriteLine("關(guān)閉 DVD 播放器");
??????? }
??? }
}
???????????????
?????????????
using System;
namespace Facade
{
??? /// <summary>
??? /// 燈光
??? /// </summary>
??? public class Light
??? {
??????? public void OpenLight()
??????? {
??????????? Console.WriteLine("打開(kāi)燈光");
??????? }
??????? public void CloseLight()
??????? {
??????????? Console.WriteLine("關(guān)閉燈光");
??????? }
??? }
}
下面再貼出外觀類中的代碼
namespace Facade
{
??? /// <summary>
??? /// 定義一個(gè)外觀
??? /// </summary>
??? public class MovieFacade
??? {
??????? /// <summary>
??????? /// 在外觀類中必須保存有子系統(tǒng)中各個(gè)對(duì)象
??????? /// </summary>
??????? private Projector projector;
??????? private Amplifier amplifier;
??????? private Screen screen;
??????? private DVDPlayer dvdPlayer;
??????? private Light light;
??????? public MovieFacade()
??????? {
??????????? projector = new Projector();
??????????? amplifier = new Amplifier();
??????????? screen = new Screen();
??????????? dvdPlayer = new DVDPlayer();
??????????? light = new Light();
??????? }
? ????? /// <summary>
??????? /// 打開(kāi)電影
??????? /// </summary>
??????? public void OpenMovie()
??????? {
? ????????? //先打開(kāi)投影儀
??????????? projector.OpenProjector();
????? ????? //再打開(kāi)功放
??????????? amplifier.OpenAmplifier();
???? ?????? //再打開(kāi)屏幕
???? ?????? screen.OpenScreen();
??????? ??? //再打開(kāi) DVD
????????? ? dvdPlayer.OpenDVDPlayer();
?? ???????? //再打開(kāi)燈光
????? ????? light.OpenLight();
??????? }
? ????? /// <summary>
??????? /// 關(guān)閉電影
??????? /// </summary>
??????? public void CloseMovie()
??????? {
? ????????? //關(guān)閉投影儀
??? ??????? projector.CloseProjector();
??????????? //關(guān)閉功放
?? ???????? amplifier.CloseAmplifier();
????????? ? //關(guān)閉屏幕
????????? ? screen.CloseScreen();
??? ??????? //關(guān)閉 DVD
??????????? dvdPlayer.CloseDVDPlayer();
??????????? //關(guān)閉燈光
????????? ? light.CloseLight();
??????? }
??? }
}
最后貼出客戶端代碼
using System;
namespace FacadeTest
{
??? class Program
??? {
??????? static void Main(string[] args)
??????? {
??????????? Facade.MovieFacade movie = new Facade.MovieFacade();
??????????? Facade.Projector projector = new Facade.Projector();
???????????????
??????????? //首先是觀看電影
??????????? movie.OpenMovie();
??????????? Console.WriteLine();
????????????
??????????? //然后是將投影儀模式調(diào)到寬屏模式
??????????? projector.SetWideScreen();
?????????? //再將投影儀模式調(diào)回普通模式
??????????? projector.SetStandardScreen();
??????????? Console.WriteLine();
??????????
??????? ??? //最后就是關(guān)閉電影了
??????????? movie.CloseMovie();
??????????? Console.ReadKey();
??????? }
??? }
}
效果如下
從上面的截圖可以看出,我還是可以在客戶端中使用子系統(tǒng)中的內(nèi)容,即外觀模式并沒(méi)有把子系統(tǒng)和客戶端隔離開(kāi)來(lái),
其只是提供了整潔的接口給客戶端,但是,如果客戶端想訪問(wèn)復(fù)雜子系統(tǒng)中的接口時(shí)還是一樣的可以訪問(wèn)的,
比如在上面的 Demo 中就訪問(wèn)了子系統(tǒng)中的投影儀,并且設(shè)置了寬屏和普通等模式。
從上面的 Demo 中也可以清晰地看出,
外觀模式可以提供一個(gè)簡(jiǎn)潔的外觀接口來(lái)實(shí)現(xiàn)將一個(gè)復(fù)雜的子系統(tǒng)變得容易使用。
同時(shí),在客戶端還是可以訪問(wèn)原來(lái)子系統(tǒng)中的復(fù)雜的接口的。
好了,外觀模式的介紹就到這里了。
???????????
??????????
?????????????
下面將要介紹的是裝飾者模式,適配器模式,外觀模式三者之間的區(qū)別:
裝飾者模式的話,它并不會(huì)改變接口,而是將一個(gè)一個(gè)的接口進(jìn)行裝飾,也就是添加新的功能。
適配器模式是將一個(gè)接口通過(guò)適配來(lái)間接轉(zhuǎn)換為另一個(gè)接口。
外觀模式的話,其主要是提供一個(gè)整潔的一致的接口給客戶端。
總結(jié)
以上是生活随笔為你收集整理的适配器模式(Adapter)和外观模式(Facade)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 用js写简单选项卡
- 下一篇: js-window对象的方法和属性资料