在系列一中, RM和MPEG類繼承了VideoMedia抽象類別,而VideoMedia類又實現了IMedia介面,該介面僅僅提供了Play()方法。冰汽水的意思是希望為RM,MPEG提供與AudioMedia不同的屬性和方法。例如,對於視頻媒體而言,應該有一個調整畫面大小的方法,如Resize()。而這個方法是IMedia介面所不具備的。
那麼怎樣為RM,MPEG類提供IMedia介面所不具備的Resize()方法呢?非常自然地,通過這個問題我們就引出Adapter模式的命題了。首先,要假設一個情況,就是原文的所有代碼,我們是無法改變的,這包括暴露的介面,類與介面的關係等等,都無法通過編碼的方式實現新的目標。只有這樣,引入Adapter模式才有意義。
熟悉Adapter模式的人都知道,Adapter模式分為兩種:類的Adapter模式、對象的Adapter模式。下面我試圖根據本例對兩種方式進行說明及實現。在實現Adapter模式之前,有必要看看原來的類結構:
左邊橙色的類為音頻媒體類型,右邊藍色的類為視頻媒體類型。所有的這些類型,包括類和介面都是無法改變的。現在我們的目的就是要讓RM、MPEG具有Resize()方法。那麼首先定義一個介面IVideoMedia,該介面具有Resize()方法。
下面我們就根據Adapter模式來實現需求。
一、類的Adapter模式
既然要讓RM、MPEG具有Resize()方法,最好的辦法就是讓它們直接實現IVedioScreen介面。然而受到條件的限制,這兩個類類型是不可修改的。唯一可行的辦法就是為相應的類新引入一個類類型,這就是Adapter模式中所謂的Adapter類了。它好比是一個轉接頭,通過它去實現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.”);
}
}
也許很多人已經注意到了,在使用這種方式建立Adapter時,存在一個局限,就是我們必須為每一個要包裹(Wrapping)的類,建立一個相應的Adapter類。如上所述的RM對應RMAdapter,MPEG對應MPEGAdapter。必須如此,為什麼呢?雖然RM和MPEG繼承了同一個抽象類別VedioMedia,但其Play()方法,可能是不相同的。此時,相對應的Adpater類只有直接繼承該具體類,方才可以保留其原來的Play()方法的行為本質。
OOP中很重要的思想是,盡量使用彙總而非繼承。讓我們換一種思路來考察Adapter模式。
二、對象的Adapter模式
對象的Adapter模式,與類的Adapter模式,最大的區別就是不採用繼承的方式,而是將要包裹的對象,以彙總的方式放到Adapter中,然後用委託的方式調用其對象的方法,實作類別圖如下:
比較兩種實現方式的類圖,可以得出兩個結論:
1、對象的Adapter模式,減少了對象的個數;
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直接發生關係,並通過在建構函式傳遞參數的方式,等待用戶端使用Adapter時,才將具體的VedioMedia對象傳遞給Adapter,顯得耦合度更加鬆散,更加靈活。
我們來看用戶端調用時,兩者的區別:
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模式,還可以做一些改進,就是用屬性或方法來取代建構函式傳遞被包裹對象的方式。代碼修改如下:
public class VedioAdapter:IVedioScreen
{
private vedioMedia _vedio;
public VedioMedia Vedio
{
set {_vedio = value;}
}
……
}
這樣,上面的用戶端調用就更簡單了:
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模式的實質,也是為什麼取名為Adapter的原因之所在了。同時,我們要注意的是,在運用Adapter模式時,必須審時度勢,根據具體的情況,抉擇最優的方式,或者採用類的Adapter模式,或者採用對象的Adapter模式。決定權在與你,菜單給你送上來了,看看自己的腰包,想想點什麼樣的菜吧。