|
從Adapter模式到Decorator模式 作者/來源: Bruce Zhang
一、考察對象的Adapter模式 從上文看到,經(jīng)過引入Adapter模式,原有的結(jié)構(gòu)得到了改進。但我們還需要從客戶的角度分析程序,使結(jié)構(gòu)更加地合理。(這里,我們僅限于考察對象的Adapter模式。類的Adapter模式不存在下述問題。這也印證了一個事實,就是:對象的Adapter模式和類的Adapter模式各有優(yōu)勢,也各有缺點,設計時應根據(jù)實際情況考察。) 1、擴展的功能是否合理? 假設用戶希望調(diào)用VedioMedia同時具有Play()和Resize()功能。從前面的描述來看,客戶只需要實例化VedioAdapter類對象,就可以調(diào)用了。看來結(jié)構(gòu)是正確的。 2、類型的擴展是否合理? 從目前的需求來看,要調(diào)用RM和MPEG類型的對象,沒有任何問題。但是正如呂震宇所說,在VedioMedia類的Resize()方法中有一股腐化的味道。壞味道的根源就是if 條件語句。如果要增加新的視頻類型,就需要修改Resize()方法了。這是一個設計的權(quán)衡。其實這個味道雖然夠壞,但好處是簡單,也不用更多的對象;但耦合性比較差。 如果我們的目標是希望更好的架構(gòu)以支持耦合的松散,目前的結(jié)構(gòu)就需要微調(diào)了。調(diào)整后的類圖如下:
這樣需要改變VedioAdapter類的代碼:
public
abstract
class VedioAdapter:IVedioScreen
{
protected vedioMedia _vedio;
public VedioAdapter(vedioMedia vedio)
{
_vedio = vedio;
}
public
void Play()
{
_vedio.Play();
}
public
abstract
void Resize();
}
然后實現(xiàn)RMAdapter和MPEGAdatper:
public
class RMAdapter:VedioAdapter
{
public RMAdapter(VedioMedia vedio):base(vedio){}
public
override
void Resize()
{
MessageBox.Show(”Change the RM screen’s size.”);
}
}
(MPEGAdapter的代碼省略) 這樣一改,要擴展就容易了,不過比之以前設計要復雜些,希望不會有人說我過度設計。如果要考慮正確性的話,RMAdapter的構(gòu)造函數(shù)還需要考慮異常情況。由于構(gòu)造函數(shù)的參數(shù)為VedioMedia類型,因此,客戶在調(diào)用時可能會傳入MPEG類型,此時RMAdapter類型的Play()行為就會發(fā)生改變。這也是和Decorator最大的不同,就是我們必須限制對象的Play()方法不能做任何改變。
public RMAdapter(VedioMedia vedio):base(vedio)
{
if (!(vedio is RM))
throw
new Exception(”VedioMedia object
is not correct!”);
}
3、是否與原有客戶系統(tǒng)兼容? 如果在原有的客戶系統(tǒng)中提供了如下的類及方法:
public
class MediaFile
{
public
static
void Play(IMedia media)
{
media.Play();
}
}
那么客戶如下的調(diào)用是沒有任何問題的: MediaFile.Play(new RM()); 然而,當客戶要使用新的Adapter對象呢?例如: MediaFile.Play(new RMAdapter()); 顯然是有問題了,因為RMAdpater類沒有實現(xiàn)IMedia接口,且RMAdpater類的Play()方法和IMedia接口的Play()方法在性質(zhì)上也是有區(qū)別的。此時,采用原有的設計就不正確了。改進的方法很簡單,就是讓VedioAdapter類實現(xiàn)IMedia接口就可以了:
public abstract class VedioAdapter:IVedioScreen,IMedia { …… } 根據(jù)呂震宇所說,當VedioAdapter類實現(xiàn)IMedia接口時,言外之意就是該Adapter也適合AudioMedia類型了。是否如此呢?可以說是一半對,一半不對。對的原因,是由于AudioMedia類也實現(xiàn)了IMedia接口;但別忘了,我們適配的并非類,而是對象,也就是在VedioAdapter中傳遞進來的VedioMedia對象。(如果我們將傳遞進來的對象擴展為IMedia,那就糟糕了。震宇兄的結(jié)論就完全成立了。)同時,我們在構(gòu)造函數(shù)的異常處理,也保證了AudioMedia類型對于VedioAdapter是非法的。 寫到這里,我覺得本文已經(jīng)超出了原來的設想,有些研究的味道了。嗯,還算不錯。那么就繼續(xù)研究下去吧。 二、引入Decorator模式 按照最初的需求,我引入Decorator模式試一試。最初的需求是,需要為RM和MPEG類在不改變原有代碼的情況下,添加Resize()方法,而其原來的Play()方法不變。調(diào)整設計類圖:
上圖的橙紅色區(qū)域為Decorator模式的主體,至于IVedioScreen接口,僅僅是為VedioDecorator增加Resize()方法而引入的,其本身與Decorator無關(guān),除非我要實現(xiàn)的Decorator功能,通過該接口來實現(xiàn)。代碼如下:
public
abstract
class VedioDecorator:VedioMedia,IVedioScreen
{
private VedioMedia _vedio;
public VedioAdapter(vedioMedia vedio)
{
_vedio = vedio;
}
public VedioMedia Vedio
{
get
{return _vedio;}
}
public
abstract
void Resize();
}
注意看,與前面Adapter模式的VedioAdapter類比較,除增加了對VedioMedia的派生外,還減少了Play(),因為該方法已經(jīng)從VedioMedia類中派生獲得。這樣的話,RMDecorator和MPEGDecorator,也需要做相應的改變:
public
class RMDecorator:VedioDecorator
{
public RMDecorator (VedioMedia vedio):base(vedio)
{
if (!(vedio is RM))
throw
new Exception(”VedioMedia object
is not correct!”);
}
public
override
void Play()
{
Vedio.Play();
}
public
override
void Resize()
{
MessageBox.Show(”Change the RM screen’s size.”);
}
}
到這個時候,我忽然覺得引入Decorator模式,已經(jīng)有些力不從心了。為什么呢?最大的障礙就是我們的需求不能更改VedioMedia類型Play()方法的既有行為。這個時候,所謂的Decorator已經(jīng)失去了原來的意義。其實我覺得,此時的設計,應該是結(jié)合了Adapter模式與Decorator模式而衍生出的新的結(jié)構(gòu)。 那么,我為什么還要在本文提出引入Decorator模式呢。這來源我對于設計模式一向的觀點:不要為了模式而模式!GOF的23種模式,并非茴香豆的“茴”字,我也并非孔乙己,要你回答“茴”字的寫法,卻忽略了使用設計模式的真正精神。設計模式歸根結(jié)底是拿來用的。只要符合你的要求,各種模式隨你怎么變都可以。因此,不管是前文所述的Adapter模式,還是改進后的Adapter模式,或者引入的Decorator模式,其中的變化是靈活的,選擇權(quán)最終還是你。 三、正宗的Decorator模式 不過,我還是很有興趣繼續(xù)探討下去,仍然借助媒體播放這個例子,來談一談Decorator模式的一般應用?,F(xiàn)在我們要求RM和MPEG媒體在播放前,首先要顯示媒體文件的版權(quán)信息。請注意,這個需求,并非是為RM等媒體增加ShowCopyright()方法,而Play()方法保持不變。恰恰相反,新的需求裝飾了Play()的行為,它要求Play()的同時能夠支持ShowCopyright的功能。類圖如下:
在這里,VedioDecorator是裝飾類的抽象類,而CopyRightVedioDecorator類則具體裝飾了Play()的功能。
public
abstract
class VedioDecorator:VedioMedia
{
private VedioMedia _vedio;
public VedioAdapter(vedioMedia vedio)
{
_vedio = vedio;
}
public VedioMedia Vedio
{
get
{return _vedio;}
}
}
然后實現(xiàn)CopyRightVedioDecorator類,為Play()方法裝飾顯示版權(quán)信息的功能:
public
class CopyRightVedioDecorator:VedioDecorator
{
private CopyRight _copyRightMark;
public CopyRight CopyRightMark
{
get
{return _copyRightMark;}
set
{_copyRightMark = value;}
}
public
override
void Play()
{
_copyRightMark.ShowCopyRight();
Vedio.Play();
}
}
我們還可以繼續(xù)裝飾VedioMedia的Play行為,例如,要求在播放媒體文件之前,必須放一段廣告,那么我們可以繼續(xù)提供一個AdvertisementVedioDecorator裝飾類。道理與上一樣,不再贅述。 通過本例,我們可以看到Decorator模式與對象的Adapter模式的區(qū)別。 實現(xiàn)的區(qū)別: 1、Decorator抽象類應繼承要裝飾的類,同時又聚合該類的實例對象;而對象的Adapter模式則只聚合,不繼承; 2、Decor模式并沒有引入新的接口,除非要裝飾的行為需要使用該接口;而對象的Adapter模式則引入了新的接口,以此來裝配原有的對象,使其具有了新接口的方法; 因此,適用的場景也就有所不同: 1、Decorator模式如其名,一般并不提供新的行為,而是在原有的行為上進行補充,即裝飾的含義。 2、Adapter模式則是為對象引入新的行為,使其匹配新的接口,即為適配的意義所在。 |
|
|