從實例談Adapter模式 作者/來源: Bruce Zhang
在系列一中, RM和MPEG類繼承了VideoMedia抽象類,而VideoMedia類又實現(xiàn)了IMedia接口,該接口僅僅提供了Play()方法。冰汽水的意思是希望為RM,MPEG提供與AudioMedia不同的屬性和方法。例如,對于視頻媒體而言,應(yīng)該有一個調(diào)整畫面大小的方法,如Resize()。而這個方法是IMedia接口所不具備的。 那么怎樣為RM,MPEG類提供IMedia接口所不具備的Resize()方法呢?非常自然地,通過這個問題我們就引出Adapter模式的命題了。首先,要假設(shè)一個情況,就是原文的所有代碼,我們是無法改變的,這包括暴露的接口,類與接口的關(guān)系等等,都無法通過編碼的方式實現(xiàn)新的目標。只有這樣,引入Adapter模式才有意義。 熟悉Adapter模式的人都知道,Adapter模式分為兩種:類的Adapter模式、對象的Adapter模式。下面我試圖根據(jù)本例對兩種方式進行說明及實現(xiàn)。在實現(xiàn)Adapter模式之前,有必要看看原來的類結(jié)構(gòu): 左邊橙色的類為音頻媒體類型,右邊藍色的類為視頻媒體類型。所有的這些類型,包括類和接口都是無法改變的?,F(xiàn)在我們的目的就是要讓RM、MPEG具有Resize()方法。那么首先定義一個接口IVideoMedia,該接口具有Resize()方法。 下面我們就根據(jù)Adapter模式來實現(xiàn)需求。 一、類的Adapter模式 既然要讓RM、MPEG具有Resize()方法,最好的辦法就是讓它們直接實現(xiàn)IVedioScreen接口。然而受到條件的限制,這兩個類類型是不可修改的。唯一可行的辦法就是為相應(yīng)的類新引入一個類類型,這就是Adapter模式中所謂的Adapter類了。它好比是一個轉(zhuǎn)接頭,通過它去實現(xiàn)IVedioScreen接口,同時又令其繼承原有的RM或MPEG類,以保留原有的行為。類圖如下: 圖中的類RMAdapter和MPEGAdapter就是通過Adapter模式獲得的對象,它在保留了原有行為的同時,又擁有了IVedioScreen的功能。 代碼如下: public
interface IVedioScreen { void Resize(); } public
class RMAdapter:RM,IVedioScreen { public
void Resize() { MessageBox.Show(”Change the RM screen’s size.”); } } public
class MPEGAdapter:MPEG,IVedioScreen { public
void Resize() { MessageBox.Show(”Change the MPEG screen’s size.”); } } 也許很多人已經(jīng)注意到了,在使用這種方式建立Adapter時,存在一個局限,就是我們必須為每一個要包裹(Wrapping)的類,建立一個相應(yīng)的Adapter類。如上所述的RM對應(yīng)RMAdapter,MPEG對應(yīng)MPEGAdapter。必須如此,為什么呢?雖然RM和MPEG繼承了同一個抽象類VedioMedia,但其Play()方法,可能是不相同的。此時,相對應(yīng)的Adpater類只有直接繼承該具體類,方才可以保留其原來的Play()方法的行為本質(zhì)。 OOP中很重要的思想是,盡量使用聚合而非繼承。讓我們換一種思路來考察Adapter模式。 二、對象的Adapter模式 對象的Adapter模式,與類的Adapter模式,最大的區(qū)別就是不采用繼承的方式,而是將要包裹的對象,以聚合的方式放到Adapter中,然后用委托的方式調(diào)用其對象的方法,實現(xiàn)類圖如下: 比較兩種實現(xiàn)方式的類圖,可以得出兩個結(jié)論: 1、對象的Adapter模式,減少了對象的個數(shù); 2、耦合度更加松散; 代碼如下: public
interface IVedioScreen { void Resize(); } public
class VedioAdapter:IVedioScreen { private vedioMedia _vedio; public VedioAdapter(vedioMedia vedio) { _vedio = vedio; } public
void Play() { _vedio.Play(); } public
void Resize() { if (_vedio is RM) MessageBox.Show(”Change the RM screen’s size.”); else MessageBox.Show(”Change the MPEG screen’s size.”); } } 以這種方式形成的VedioAdapter,由于沒有和RM、MPEG直接發(fā)生關(guān)系,并通過在構(gòu)造函數(shù)傳遞參數(shù)的方式,等待客戶端使用Adapter時,才將具體的VedioMedia對象傳遞給Adapter,顯得耦合度更加松散,更加靈活。 我們來看客戶端調(diào)用時,兩者的區(qū)別: 1、類的Adapter模式 public
class Client { public
static
void Main() { RMAdapter rmAdapter =
new RMAdapter(); MPEGAdapter mpegAdapter =
new MPEGAdapter(); rmAdapter.Play(); rmAdapter.Resize(); mpegAdapter.Play(); mpegAdapter.Resize(); } } 2、對象的Adapter模式 public
class Client { public
static
void Main() { VedioAdapter rmAdapter =
new VedioAdapter(new RM()); VedioAdapter mpegAdapter =
new VedioAdapter(new MPEG()); rmAdapter.Play(); rmAdapter.Resize(); mpegAdapter.Play(); mpegAdapter.Resize(); } } 其實,對于對象的Adapter模式,還可以做一些改進,就是用屬性或方法來取代構(gòu)造函數(shù)傳遞被包裹對象的方式。代碼修改如下: public
class VedioAdapter:IVedioScreen { private vedioMedia _vedio; public VedioMedia Vedio { set
{_vedio = value;} } …… } 這樣,上面的客戶端調(diào)用就更簡單了: public
class Client { public
static
void Main() { VedioAdapter adapter =
new VedioAdapter(); adapter.Vedio =
new RM(); adapter.Play(); adapter.Resize(); adapter.Vedio =
new MPEG(); adapter.Play(); adapter.Resize(); } } 通過運用Adapter模式,擴展了新的接口,而原有的類型并不需要做任何改變,這就是Adapter模式的實質(zhì),也是為什么取名為Adapter的原因之所在了。同時,我們要注意的是,在運用Adapter模式時,必須審時度勢,根據(jù)具體的情況,抉擇最優(yōu)的方式,或者采用類的Adapter模式,或者采用對象的Adapter模式。決定權(quán)在與你,菜單給你送上來了,看看自己的腰包,想想點什么樣的菜吧。 |
|
來自: ljjzlm > 《設(shè)計模式》