從容自若的CTO
讓我們假設(shè)這樣一個(gè)場(chǎng)景:一年以前,Media公司開(kāi)發(fā)出一套通過(guò)電腦接收廣播的Radio仿真軟件產(chǎn)品。(有這樣的產(chǎn)品嗎,能真正接收廣播的軟件?我表示懷疑)這個(gè)產(chǎn)品早已投入市場(chǎng),客戶已經(jīng)在使用了。后來(lái),Media公司將開(kāi)發(fā)重心轉(zhuǎn)移到數(shù)字媒體上。于是他們投入了大量的人力物力,最后開(kāi)發(fā)出了完美的媒體播放器軟件。這個(gè)播放器支持大多數(shù)媒體文件,包括音頻媒體和視頻媒體。該產(chǎn)品取得了成功,也得到了用戶的好評(píng)。
不過(guò),現(xiàn)實(shí)生活中總有些刁鉆的客戶,比如說(shuō)wayfarer,就是鄙人了,素愛(ài)懷舊。在使用媒體播放器的時(shí)候,想起了在初中的時(shí)候就使用的收錄機(jī)。磁帶、廣播,一機(jī)兩用,真是令人懷念。于是我向Media公司提出了建議,希望能在媒體播放器中增加收音的功能。Media的CEO對(duì)這個(gè)似乎有些嗤之以鼻??墒窍駑ayfarer這樣的用戶越來(lái)越多,呼聲也越來(lái)越高。為了產(chǎn)品的市場(chǎng),為了公司的前景,這位CEO不得不慎重考慮這個(gè)需求了。當(dāng)首席執(zhí)行官就是好,趕緊把這個(gè)燙手山芋拋給了CTO。
卻看這位CTO仍然是從容不迫,臉上掛滿自信的微笑。CEO不解,問(wèn)他何故如此從容?CTO淡然一笑,吐出一字真言:“Adapter”。
呵呵,笑話了。設(shè)計(jì)模式可不是什么Bible,也非神奇的魔咒。不過(guò)對(duì)于以上場(chǎng)景,使用Adapter卻是最佳的應(yīng)用!且請(qǐng)聽(tīng)我慢慢道來(lái)。
已有產(chǎn)品:MediaPlayer、RadioPlayer;
分析:MediaPlayer是面向客戶的外觀,即表示層,它調(diào)用了對(duì)應(yīng)的業(yè)務(wù)層,該層實(shí)現(xiàn)了IMedia接口。同理RadioPlayer也是面向客戶的外觀,它調(diào)用的業(yè)務(wù)層,是收聽(tīng)廣播的業(yè)務(wù),并實(shí)現(xiàn)了IRadio接口。
目的:將RadioPlayer的業(yè)務(wù)添加到MediaPlayer的外觀中。原有的RadioPlayer不再使用。
既然與MediaPlayer、RadioPlayer的業(yè)務(wù)有關(guān),所以我們有必要分析其各自的業(yè)務(wù)結(jié)構(gòu)。MediaPlayer業(yè)務(wù)層結(jié)構(gòu):

為了簡(jiǎn)化,我這里將所有的方法都放在一個(gè)接口IMedia里(這個(gè)設(shè)計(jì)還有很多重構(gòu)的空間,我會(huì)在后續(xù)文章中繼續(xù)關(guān)注)。在本文的結(jié)構(gòu)中,視頻媒體和音頻媒體的方法是相同的,本來(lái)我可以令各媒體文件繼承同一個(gè)抽象類(lèi)Media。但現(xiàn)實(shí)情況顯然不是這樣,所以我仍然保留這個(gè)系列文章中原有的結(jié)構(gòu)。以下是每個(gè)方法的說(shuō)明:
Play():播放媒體文件;
Stop():停止播放;
Pause():暫停播放;
OpenFile():打開(kāi)媒體文件;
CloseFile():關(guān)閉媒體文件;
Forward():前進(jìn)播放文件;
Back():后退播放文件;
OK,我們?cè)賮?lái)看看RadioPlayer的業(yè)務(wù)層結(jié)構(gòu):

RadioPlayer的業(yè)務(wù)均抽象為IRadio接口。并由抽象類(lèi)Radio實(shí)現(xiàn)該接口。FM為調(diào)頻收音,SW為短波收音。另外還有其他的,例如中波等,就不在詳細(xì)列出。各方法的功能說(shuō)明如下:
Receive():接收廣播;
Stop():停止接收廣播;
TurnOn():打開(kāi)收音;
TurnOff():關(guān)閉收音;
ChangeChannel(bool direction):切換頻率。參數(shù)direction為true時(shí),則往上;否則往下。當(dāng)然也可以使用枚舉類(lèi)型。
媒體播放器的業(yè)務(wù)由一個(gè)統(tǒng)一的Client類(lèi)進(jìn)行處理,它包括一系列的靜態(tài)方法以實(shí)現(xiàn)對(duì)原有媒體類(lèi)型的調(diào)用:
public class Client
{
public static void Play(IMedia media)
{
media.Play();
}
public static void OpenFile(IMedia media)
{
media.OpenFile();
}
//……其他方法略;
}
MediaPlayer播放器本身,其外觀則是一個(gè)WinForm應(yīng)用程序,該應(yīng)用程序?qū)⒄{(diào)用Client的相關(guān)靜態(tài)方法。如:
Client.Play(new MP3());
現(xiàn)在看看我們需要實(shí)現(xiàn)的。我需要將RadioPlayer的業(yè)務(wù),即抽象為IRadio接口的對(duì)象,放到MediaPlayer中。糟糕的是,Client的各個(gè)方法傳遞的參數(shù)類(lèi)型,為IMedia接口。怎么才能將實(shí)現(xiàn)IRadio接口的對(duì)象傳遞到Client的方法中呢?對(duì)了,這就是適配,就是為IRadio對(duì)象適配成符合IMedia接口行為的過(guò)程。打一個(gè)不好聽(tīng)的比方,就好比一只狼,要讓自己鉆進(jìn)羊群里,而不被發(fā)現(xiàn),就需要找一張羊皮來(lái)披上。俗語(yǔ)云:“披著羊皮的狼”是也。不過(guò),我們要注意的是,狼雖然不是羊,但有著和羊相似的屬性。它和羊體形相似,照樣能跑,能吃,只是吃的不是草,而是肉而已。你總不能為一張桌子披上羊皮,去裝羊吧。而文中的IMedia類(lèi)型和IRadio類(lèi)型,還是有很多相似之處的。
現(xiàn)在,我們就為IRadio接口進(jìn)行適當(dāng)?shù)陌b。由于這是兩個(gè)接口進(jìn)行匹配的過(guò)程,所以我們通常名之為“適配”,而非“包裹”。那么它們之間有相似性嗎?有!
IMedia IRadio
Play() Receive()
Stop() Stop()
OpenFile() TurnOn()
CloseFile() TurnOff()
Forward() ChangeChannel(true)
Back() ChangeChannel(false)
當(dāng)然現(xiàn)實(shí)情況并非總是那么完美。可能IMedia的方法中,IRadio可能并不需要。沒(méi)關(guān)系,我們只提供該方法就可以了,方法的實(shí)現(xiàn)可以為空,如Pause()方法。也有可能IRadio的一些方法,IMedia并沒(méi)有,此時(shí)的Adaptor模式,就將被適配對(duì)象的接口變寬了,也就是說(shuō)引入了新的行為,這就類(lèi)似于我系列文章之二所描述的。
不管現(xiàn)實(shí)的某些情況是多么的不如意,但至少通過(guò)引入Adapter模式,我們就不需要改變?cè)械腎Media和IRadio的相關(guān)對(duì)象與業(yè)務(wù)了。要修改的,僅僅是客戶端,以及增加一個(gè)新的Adapter結(jié)構(gòu)而已。
分析結(jié)束,開(kāi)始動(dòng)手術(shù)吧。先看類(lèi)的Adapter模式:

類(lèi)圖好像很復(fù)雜,不過(guò)請(qǐng)大家主要關(guān)注橙色的兩個(gè)類(lèi)FMAdapter和SWAdapter。FMAdapter類(lèi)是FM類(lèi)型的Adapter,它繼承了FM類(lèi),并實(shí)現(xiàn)了IMedia接口。通過(guò)這種方式,原有的FM類(lèi)型的行為,就被適配為符合IMedia類(lèi)型的新類(lèi)型。代碼如下:
public class FMAdapter:FM,IMedia
{
public void Play()
{
this.Receive();
}
public void Forward()
{
this.ChangeChannel(true);
}
public void Pause(){}//Radio類(lèi)型沒(méi)有該行為,令其為空,或引入異常機(jī)制;
//其他方法略
……
}
SWAdapter的實(shí)現(xiàn)方式完全相同,就不贅述。
由于新的Adapter類(lèi)均實(shí)現(xiàn)了IMedia接口,因此,該類(lèi)型的對(duì)象可以安全正確地作為Client靜態(tài)方法的參數(shù)對(duì)象傳入。從外部行為的表現(xiàn)來(lái)看,沒(méi)有區(qū)別。如:
Client.Play(new FMAdapter());
它調(diào)用了FMAdapter的Play方法,而其內(nèi)部,實(shí)質(zhì)上調(diào)用的是FM的Receiver()方法。
再看對(duì)象的Adapter模式,就更簡(jiǎn)單了。

只需要一個(gè)Adapter類(lèi)RadioAdapter,然后實(shí)現(xiàn)IMedia接口。沒(méi)有繼承關(guān)系了,而是聚合了Radio對(duì)象。注意,這里聚合的是抽象類(lèi)對(duì)象Radio,而不是具體的FM或SW。
public class RadioAdapter:IMedia
{
private Radio _radio;
public RadioAdapter(Radio radio)
{
this._radio = radio;
}
public void Play()
{
_radio.Receive();
}
public void Forward()
{
_radio.ChangeChannel(true);
}
public void Pause(){}//Radio類(lèi)型沒(méi)有該行為,另其為空,或引入異常機(jī)制;
//其他方法略
……
}
調(diào)用Client的靜態(tài)方法:
Client.Play(new RadioAdapter(new FM()));
通過(guò)引入Adapter模式,我們?cè)诓桓淖冊(cè)蠭Media和IRadio的情況下,順利地將IRadio類(lèi)型適配成了IMedia類(lèi)型。此時(shí),我們只需要在MediaPlayer的客戶端調(diào)用中加入原來(lái)RadioPlayer的業(yè)務(wù)即可,基本保證了原有系統(tǒng)的穩(wěn)定性。
上述實(shí)例,才真正體現(xiàn)了Adapter的價(jià)值(請(qǐng)大家一定注意區(qū)分本文實(shí)例需求,與系列之二實(shí)例需求的區(qū)別)。因此,我們可以得到兩個(gè)結(jié)論:
1、通過(guò)Adapter模式,為適配對(duì)象引入以前不具備的行為;此時(shí)建議使用類(lèi)的Adapter模式。理由請(qǐng)參考:系列文章之二與之三;
2、將一個(gè)固有對(duì)象適配為另一種接口對(duì)象;這是Adapter模式最重要的功能。使用類(lèi)的Adapter模式與對(duì)象的Adapter模式均可,但感覺(jué)使用對(duì)象的Adapter模式更簡(jiǎn)單。
怎么樣,夠簡(jiǎn)單吧?難怪我們的CTO如此從容,因?yàn)樗呀?jīng)找到了終南捷徑!




