ADAPTER模式 —— 田單火燒牛尾破燕軍
junguo
Adapter的中文翻譯是適配器,它的目的是將一個類的接口轉化為另外一個類的接口,這樣可以使原本由于接口不同而不能一起工作的類可以一起工作。這個模式比較簡單,我們還是通過例子來具體看看。
戰國時期,燕國大將樂毅帶兵攻打齊國。五年的時間,占領齊國七十多座城池,只剩兩城莒和即墨還在堅守,但也兵少勢薄,眼看著齊國就要被滅。而一個人改變了齊國的命運,這個人就是田單。樂毅攻齊的時候,田單不過是一個齊國不知名的小官。他和別人一樣無法掌握自己的命運,等樂毅大軍到來的時候,他也不得不逃跑。在逃跑的過程中,他顯示出了自己過人的智慧。他讓自己宗族的人,把車軸兩端突出的部分全部鋸掉,并用鐵箍將車輪包上。逃跑的時候,場面混亂,大多車輛被撞的軸斷車壞,做了燕國的俘虜。而田單和他的族人順利的逃跑了出來。田單來到了即墨,這時候即墨大夫出城迎擊燕軍,結果戰死疆場。這時候大家由于田單逃跑過程中表現出的智慧,選舉他做了將軍,對抗燕軍。趕上這時候燕國國君昭王逝世,而他的繼任者惠王與樂毅之間有矛盾。田單使反間計,讓惠王撤換了樂毅,而樂毅無奈之下逃到了趙國。
走了樂毅,田單開始了自己的反攻大計。他先是讓城中的人吃飯的時候必須祭祖,結果飛鳥都跑到城中來覓食,城外的燕軍都感覺奇怪。他對別人說:神來救我們了,而且神人來做我的老師。他選了一個小卒作了他的老師,對別人說這是神師。每次發號施令,都說是神師的主意。然后他告訴別人:我最怕的是燕國人割掉齊國降兵的鼻子,作戰的時候讓這些人走在隊伍的前面,這樣的話,齊國就敗了。結果城外的燕軍聽到這樣的消息,真的割掉了齊國降兵的鼻子。這樣一來,齊國守軍都大怒,堅定了守城的決心。接著他又說:我還怕燕軍掘我們城外的墳墓,這樣就羞辱我們的先人了,是件很寒心的事。接口燕軍真的開始挖掘墳墓,把死人拿出來燒。城中的人看到這樣的情景,都痛苦流涕,都準備與燕軍死戰到底。這時候田單又找人給燕軍送禮物,并對燕軍說,齊國人準備投降了。結果燕國放松了警惕。
這時候田單在城中找了一千多頭牛,并作了大紅綢的衣服,并在衣服上面畫上了五顏六色的龍形。把武器綁在了牛角上,在牛尾上綁了用油脂浸灌過的蘆葦。在半夜的時候,在城里鑿開數十個缺口,點燃牛尾把牛放了出去。牛尾受熱,向燕軍沖了過去。燕軍大亂,看到的是五彩龍形,碰到非死即傷。這時候田單又組織城中的人敲鑼打鼓,搞的驚天動地。燕軍心膽俱裂,只知向前逃竄,田單帶人一路追殺,結果齊國失去的七十多座城池也都收復了回來。
太史公評價到:兵以正合,以奇勝。善之者,出奇無窮。奇正還相生,如環之無端。夫始如處女,適人開戶;後如脫兔,適不及距:其田單之謂邪!
看完這段故事,開始我覺得燕國將領也太可愛了,被田單牽著鼻子走。后來想想,連李洪志這種白癡的法?輪功(靠,這個詞提都不讓提,不在中間插入字符根本發布不了)都有人信,也沒有什么事情想不通了。如果讓田單也搞邪教,估計要比李大師強幾百倍。
好了,故事扯到這里了。接著描述我們要用到的例子,上面的故事中出現了牛群破敵的例子。牛群作為一個對象出現在了我們的程序中,我們為它定義了一個類:
~~~
class Bulls
{
public:
void Wrestle();
};
~~~
??? 而在我們之前出現過的例子中,有很多Army的例子:
~~~
class Army
{
public:
void Assault(){}
};
~~~
事實上牛群和軍隊的大部分操作都類似(如這里的Assault和Wrestle都代表攻擊),只是接口并不一樣。現在我們想把牛群也加到這樣的程序中,并且使它們可以一起運行。因為之前我們的程序中,可能有很多這樣的代碼:
~~~
void test(Army *pArmy)
{
pArmy->Assault();
}
~~~
我們不想去改變這些代碼。那該怎么辦呢?這就是Adapter的作用了。先看類圖:

很簡單了,就是先增一個新類BullsArmy,讓它繼承Army。而聚合Bulls,通過對Bulls的調用來實現Army的功能。
我們還是簡單看看代碼:
~~~
class Bulls
{
public:
void Wrestle()
{
cout << "牛群在攻擊" << endl;
}
};
class Army
{
public:
void virtual Assault()
{
cout << "軍隊在攻擊" << endl;
}
};
//新增的適配器類
class BullsArmy : public Army
{
private:
Bulls *m_pBulls;
public:
BullsArmy()
{
m_pBulls = new Bulls();
}
~BullsArmy()
{
delete m_pBulls;
}
void Assault()
{
m_pBulls->Wrestle();
}
};
~~~
再來看看它的調用:
~~~
void test(Army *pArmy)
{
pArmy->Assault();
}
int main(int argc, char* argv[])
{
Army* pArmy = new Army;
Army *pBulls = new BullsArmy();
test(pArmy);
test(pBulls);
return 0;
}
~~~
這樣我們就將牛群和軍隊統一了起來,可以讓它們一起合作。以上提供的方法中我們通過BullsArmy聚合Bulls來實現了功能。事實上也可以通過繼承的方式來完成,我們還是簡單看看類圖:

這樣BullsArmy改為了從Army和Bulls來繼承,仍然是通過調用Bulls的功能來實現Army的功能。這里需要注意的一點是,BullsArmy對Bulls的繼承應該采取私有繼承,這樣Bulls的成員函數都成了它的私有成員函數,就不會把這些信息暴露給用戶。我們還是簡單看看代碼,變化的只有BullsArmy的代碼。
~~~
class BullsArmy : public Army,private Bulls
{
public:
void Assault()
{
Wrestle();
}
};
~~~
可以看到這兩種方式都可以完成類的適配。前一種是通過聚合而來的,屬于對象適配器;而后一種是通過繼承完成的,屬于類適配器。我們簡單對比一下它們的優缺點:類配器相對簡單一些,不需要保存被適配類的指針。而且通過重載可以很容易的改變被適配類原有的功能。但不夠靈活,如當Bulls類有子類的時候,我們想適配它所有的子類,通過類適配器,是無法直接通過父接口來適配所有子類的,你可能需要為每個子類來填加適配器。如果你是通過對象適配器,就很容易實現這樣的功能了。
適配器在實際開發中應用的比較廣泛,概念也被擴展。適配器是STL的重要內容之一,感興趣的可以找STL資料看看。
參考書目:
1, 設計模式——可復用面向對象軟件的基礎(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英軍等譯 機械工業出版社
2, Head First Design Patterns(影印版)Freeman等著 東南大學出版社
3, 道法自然——面向對象實踐指南 王詠武 王詠剛著 電子工業出版社
4, 史記 網上找到的電子檔