BRIDGE模式 —— 所謂伊人,在水一方
junguo
Bridge模式的中文名稱是橋接模式,該模式的目的是將抽象部分和它的實現部分分離,使它們都可以獨立的變化。繼續以例子來完成對該模式的學習。
??? 蒹葭蒼蒼,白露為霜。所謂伊人,在水一方。
??? 溯洄從之,道阻且長。溯游從之,宛在水中央。
???
??? 蒹葭凄凄,白露未晞。所謂伊人,在水之湄。
??? 溯洄從之,道阻且濟。溯游從之,宛在水中坻。
???
??? 蒹葭采采,白露未已。所謂伊人,在水之涘。
??? 溯洄從之,道阻且右。溯游從之,宛在水中沚。
這首《蒹葭》取自《詩經》。王國維在他的《人間詞話》中評述說:“《詩?蒹葭》一篇,最得風人深致”。簡單的場景切換,最直接的感情描寫,成了千百年來最能體現詩人情致的詩。我感覺詩在形式上最重要的是它的語言,簡單凝練是真正的境界。現今的白話詩,大多被寫的晦澀難懂,讀完后佩服詩人到無話可說,寫的字都認識,但是就不明白放到一塊是什么意思。一直搞不懂為什么要寫成那樣?白話文寫的詩歌比古文都難懂很多,有人說這是一個不需要詩人的時代,但我想也許是詩人漂離了時代,他們也許走到了時代的前列,但我又覺得不見得過多少年后就會有人愿意花時間去琢磨晦澀的東西。除了形,就是意了。詩歌在意上追求境界,應該給人遐想的空間。如“有的人死了,他還活著;有的人活者,他已經死了”直白到無任何情趣的詩,也讓人毫無興趣;但這樣的詩一度是中國詩的主流,而穆旦那樣的優秀詩人則被排擠出了主流,排擠到無法繼續寫詩的地步。繼續說《蒹葭》,讀完詩歌會感覺到這樣一個場景,青山綠水,遠處隱隱約約一個少女的身影。近處一個男子在急切的尋找著船只,眺望著遠方的路。而他最終沒找到船,望不到路,焦慮憂傷盡顯臉上。也許你會和我一樣發現,這個尋路人就是自己。我在想程序是否也有境界,這個境界又該有什么樣樣的標準呢?
這樣的詩歌意境也許可以做成一款游戲。記得上學的時候有款游戲《心跳回憶》,類似于養成游戲,游戲的內容就是玩家通過不斷的邀請游戲中的美女約會,送禮物給美女等方式來增加美女對玩家的好感,好感增加到一定程度,她就會接受玩家的求婚。很少有人有耐心玩完這款破游戲,但我們同學中有位仁兄樂此不疲。考試前幾個鐘頭還在玩,結果考后掛了幾科要降級了。當得知降級的噩耗的時候,哥們掉了幾顆眼淚,狠狠心又坐到了電腦面前,繼續他的《心跳回憶》,10多個小時過后,當大家還都在夢鄉的時候。他就各個宿舍游蕩,看到有人醒來就異常興奮得告訴人家,《心跳回憶》中的女主角他又追到了一個。降級的郁悶就這樣被掃空了。現在,聽說這位兄弟找到的女朋友很漂亮。看到仍是單身的同學,也許他該竊笑了:得虧哥們當年還練過。
我們要實現的功能沒有那么復雜了,此處主要是設想幫游戲設計一個場景,把青山綠水畫到屏幕上。由于山可能不只一座,水也可能有多處。我們想把它們封裝成單獨的類,由客戶隨意去組合,組合成不同形狀的圖景。那么我們的類圖可以提煉成如下的形式:

有了類圖,我們就可以進行開發了。一般來說,游戲開發的圖景是寫實性質的,類似于西方油畫。但經調查發現,有這么一幫書呆子,聽到“書中自有顏如玉”的古訓后,遍翻古書尋找美女,后來發現不過癮,想到游戲中尋找自己的夢中情人。這批人酷愛中國古典文化,重意不重形,喜歡國畫上的美女,當然山水也應該傳統的水墨畫。為了吸引這批呆子,決定在游戲中提供水墨畫的場景。這樣我們的類也需要進行相應的變化,它需要支持油畫和水墨兩種風格。想想,我們該如何實現這一功能呢?最直接想到的就是通過繼承來實現,那么我們的類圖可能變成這樣。

圖中我們幫油畫和水墨都提供了接口,而山水則分別通過油畫和水墨來繼承。這樣明顯的問題是,我們需要為山水各提供兩套代碼。有這個必要嗎?只是造圖風格上不同,但過程是一樣的。接著想,如果我們新添加一個新場景進來,比如說天空和陸地,也需要單獨的類來實現。那么這時候我們又要為每個場景添加兩個類。接著想,后來發現,有哥們喜歡后印象主義畫派,還有哥們喜歡抽象主義(寫完后,想想了,發現抽象主義還是比較容易實現的,畫個三角形代表山,畫兩個弧線代表水,只要顏色取絢了就是抽象作品了),那么再把這些風格都加入到該模式中,會發生什么情形呢?完了,已經實現過的山水類都需要重新實現一次。接著想,客戶端調用又會有什么情形呢?好多類啊,該選哪個呢?當然你也可以幫他實現一個抽象工廠來減輕負擔,但這個工廠的實現一樣煩瑣。看到了這么多問題,其實總結起來原因就是一個,通過繼承我們把風格和具體的實物(山,水等)固定到了一起,無法抽離出來,導致了類的膨脹。那么該如何解決這個問題呢?方法就是把這兩樣東西給分離出來,讓它們各自實現各自的代碼。怎么實現呢?來看類圖:

我們把不同風格給分離了出來。它們有一個基類Img,主要用來實現基本的畫圖功能,比如畫線,畫圈等內容(我們這邊只簡單列出了畫垂直線和水平線)。而我們的場景也是一個單獨的類,在這些類中調用Img接口提供的基本畫圖功能來實現不同的圖形。抽象出來后,我們再回頭看上面提到的問題。假如我們添加一個具體的實物進來,比如說要添加天空進來,那么我們只要添加一個類就好了,只要調用保證它調用Img接口來實現就可以有不同的風格了。如果需要添加另一種風格進來,如后印象主義風格,那我們也不需要改變原有的類,只要先添加一個類進來就好了。擴展是不是方便了很多?還是來看看模擬的代碼(簡陋了些,我只能用文字表達表達了),先來看看風格類所需要的代碼:
~~~
//風格接口
class Img
{
public:
//畫水平線
virtual void DrawHorLine() = 0;
//畫垂直線
virtual void DrawVerLine() = 0;
protected:
Img(){}
};
//中國畫風格
class ChineseImg : public Img
{
public:
void DrawHorLine()
{
cout << "水墨畫水平線" << endl;
}
void DrawVerLine()
{
cout << "水墨畫垂直線" << endl;
}
};
//油畫風格
class CanvasImg : public Img
{
public:
void DrawHorLine()
{
cout << "油畫水平線" << endl;
}
void DrawVerLine()
{
cout << "油畫垂直線" << endl;
}
};
~~~
這邊要實現的功能其實就是提供畫圖的基本方法,比如畫線或者畫圓等動作(我不太清楚,現在圖形庫能不能幫我們實現水墨畫風格的圖形,但我想將來應該可以的)。我們這里的代碼比較簡單,不做太多的解釋。接著看看實體類的實現。
~~~
//場景抽象類
class Scene
{
public:
virtual void DrawBorder() = 0;
Img *GetImg()
{
return m_pImg;
}
virtual ~Scene()
{
delete m_pImg;
}
protected:
Scene()
{
//此處的原因,文章里分析
m_pImg = new CanvasImg();
}
private:
//擁有一個Img的指針,通過它來調用畫圖功能
Img *m_pImg;
};
//山的類
class Hill : public Scene
{
public:
void DrawBorder()
{
DrawHill();
}
void DrawHill()
{
cout << "三條垂直線就代表山了!" << endl;
Img *pImg = GetImg();
pImg->DrawVerLine();
pImg->DrawVerLine();
pImg->DrawVerLine();
}
};
//水的類
class Water : public Scene
{
public:
void DrawBorder()
{
DrawWater();
}
void DrawWater()
{
cout << "三條水平線就代表水了!" << endl;
Img *pImg = GetImg();
pImg->DrawVerLine();
pImg->DrawVerLine();
pImg->DrawVerLine();
}
};
~~~
首先注意到一點,就是Scene的構造函數的實現中有這樣的語句:“m_pImg = new CanvasImg()”,而且Scene類中沒有改邊m_pImg的方法,這樣做的原因是因為一般來說Bridge是對于整個程序風格的設置。你配置了什么樣的風格,整個程序運行的過程中風格就是什么。因為一般來說Bridge主要是適用于跨平臺的開發,在Linux中與在Windows中,畫圖的方法肯定不相同,通過Bridege可以解決這個問題,但在運行過程中肯定不應該改變Bridge的類型。可以通過配置文件來實現,這樣的話,在m_pImg生成的地方通過讀取配置文件,根據文件類型來創建m_pImg就好了。當然你想在運行過程中改變m_pImg類型也可以,看具體情況了。再看看調用:
~~~
int main(int argc, char* argv[])
{
Hill h;
Water w;
h.DrawBorder();
w.DrawBorder();
return 0;
}
~~~
通過創建的Hill或者Water類去畫圖就可以了。
我們再來看看Bridge的定義:將抽象部分和它的實現部分分離,使它們都可以獨立的變化。其實這里的抽象用的并不貼切,但想不到太好的詞,人類語言的表達能力是有限的。這里所說的抽象,并不是我們所說的接口的抽象。在例子里抽象指的就是Img及其子類了,用來畫圖的對象。而實現部分指的就是我們的Hill和Water類了。
Bridge看完了,是不是感覺有些眼熟呢?你是不是和我一樣遲鈍,想半天,還需要翻看書才想起來?回憶一下我們之前談過的策略模式,看看類圖。兩個模式不僅相似,而且相似的令人發指,完全是一樣的。但為什么會被劃成兩個模式呢?其實主要是它們要解決的問題不相同,策略模式針對的是類中所屬的單個對象或者單個方法,由于這樣的對象或者方法會有不同的實現方法,可能需要運行時刻動態改變。那么我們可以用策略模式來改變這樣的功能。而Bridge針對的是整個類,根據不同的Bridge,整個類的風格將發生變化。也可以這樣理解策略模式針對的是類中單個對象,這個對象可能影響到類中少數的幾個函數。而Bridge模式,將影響到類中決大多數的方法。
參考書目:
1, 設計模式——可復用面向對象軟件的基礎(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英軍等譯 機械工業出版社
2, Head First Design Patterns(影印版)Freeman等著 東南大學出版社
3, 道法自然——面向對象實踐指南 王詠武 王詠剛著 電子工業出版社
4, 人間詞話手稿本 王國維 著 吳洋注釋 內蒙古出版社