COMPOSITE與BUILDER模式 —— 忠義堂石碣受天文 梁山泊英雄排座次junguo Composite模式的中文名字是組合模式,該模式的目的是使單個(gè)對象和它的對象組合(一般是數組或者鏈表的結構)擁有統一的操作方式,這樣可以簡(jiǎn)化客戶(hù)的使用。我們還是通過(guò)具體的例子來(lái)理解該模式。還是先來(lái)一段例子背景介紹:
話(huà)說(shuō),宋江帶人攻陷東平東昌兩郡,收降了雙槍將董平和沒(méi)羽箭張清,而后皇甫端來(lái)歸。梁山聚集了武將,書(shū)生,醫生以至獸醫等各色人等,成為了一個(gè)成熟的社區。著(zhù)點(diǎn)人數正好一百單八人,是個(gè)吉利數字,為此宋江組織了浩大的祭天儀式。在祭天儀式進(jìn)行期間,發(fā)現了埋于地下的石碣,石碣上排定了三十六天罡,七十二地煞的座次。次序以定,梁山也開(kāi)始走上正規,開(kāi)始劃分各位好漢的職責。依據各人特長(cháng),分別任職于馬軍,步軍,軍師等角色。有了規矩,才成方圓,自此梁山告別了單挑的時(shí)代,作戰有了一些固定的組合。如李逵出戰,必有項充,李袞護在身邊;這位頭腦簡(jiǎn)單的殺人魔王的典型特點(diǎn)是攻強守弱,而且不著(zhù)盔甲,如若無(wú)人保護估摸早死了。還有另外一些組合:如矮腳虎王英和一丈青扈三娘這樣的夫妻組合(扈三娘嫁給了王英,你能想通嗎?我想不通);還有解寶解珍這樣的兄弟組合等。
來(lái)考慮一下,如何實(shí)現這樣的功能,武將可以單獨出擊,也可以成群的出擊?該如何去實(shí)現這樣的功能呢?還是一步一步來(lái),先看類(lèi)圖:
這是我們建立的武將類(lèi),該類(lèi)和我們第一講《趙子龍單騎救主》中建立的武將類(lèi)相同(為了突出中心功能,簡(jiǎn)化了功能)??紤]一下在這種情況下,我們如果想讓多個(gè)武將一起出擊,該如何實(shí)現?最單純的做法就是在調用該類(lèi)的地方生成一個(gè)數組,然后客戶(hù)端通過(guò)該數組來(lái)調用各個(gè)對象的操作。大概代碼如下:
General general[10];for( int i = 0; i<10; i++ )general[i].Assault();
客戶(hù)端也可以將這些操作封裝在不同的函數中,但如果武將類(lèi)填加新的函數的時(shí)候,客戶(hù)端也需要相應的增加此類(lèi)德函數。這是典型的封裝不好的例子,客戶(hù)端需要增加特殊的函數來(lái)支持對象。我自己的工作中就遇到了這樣的問(wèn)題,類(lèi)沒(méi)有封裝好,針對于這些類(lèi)的操作,你不得不填加一些函數,接著(zhù)就可以發(fā)現在不同的文件里有很多功能相似的函數(一個(gè)類(lèi)往往在多處用到,而這些往往是由不同的人寫(xiě)的,這樣很容易造成重復代碼)。整個(gè)程序被改得亂碼七糟的,改這樣的東西真是頭痛萬(wàn)分,更加上你如果要用到這些外部的函數的時(shí)候,都不知道用那個(gè)。那如何來(lái)解決該問(wèn)題呢?找到組合模式,就找到了解決之道。先來(lái)看看組合模式的類(lèi)圖描述:
Component是為L(cháng)eaf和Composite抽象出來(lái)的共同接口,可以通過(guò)它來(lái)統一操作單個(gè)對象或者組合對象。
Leaf是我們具體要用到的類(lèi),它用來(lái)完成具體的功能。Composite是我們用來(lái)組合對象的類(lèi),并實(shí)現對子對象的管理;它通過(guò)Add,Remove和GetChild來(lái)實(shí)現對子對象的管理。Composite中會(huì )含有一個(gè)Component型的對象列表childers,它也會(huì )重載Leaf中所需要的函數,然后通過(guò)遍歷執行所有childer的函數。該childer也可以是Composite型的,這樣就可以有一個(gè)遞歸的結構。
我們用Composite模式來(lái)重新設計我們的類(lèi)圖,如下:
理解模式最有效的方式,還是看具體的代碼:
#include #include #include #include using namespace std;//抽象部件類(lèi),是Leaf和Composite的共有接口class GeneralComponent{public://為了確保調用到析構函數,此處聲明為虛函數virtual ~GeneralComponent(){}//由于General中不實(shí)現Add,Remove,GetChild,此處需要一個(gè)缺省的實(shí)現,但此處拋出異常更合適一些,為了簡(jiǎn)單,省略了。virtual void Add(GeneralComponent* pComonent) {}virtual void Remove(GeneralComponent* pComonent) {}virtual GeneralComponent* GetChild(int i) {return NULL;}virtual void Assault() = 0;};//具體用到的類(lèi)class General : public GeneralComponent{private:string m_strName;public:General(string strName):m_strName(strName){}void Assault(){cout << m_strName << " 進(jìn)入戰斗!" << endl;}};//組合類(lèi)class GeneralComposite : public GeneralComponent{private:string m_strName;//用來(lái)存放需要組合的對象vector m_pComponents;public:GeneralComposite(string strName):m_strName(strName){}virtual ~GeneralComposite(){vector::iterator pos;for( pos = m_pComponents.begin();pos::iterator ivite=find(m_pComponents.begin(),m_pComponents.end(),pGeneral);m_pComponents.erase(ivite);}GeneralComponent* GetChild(int i){return m_pComponents[i];}void Assault(){cout << m_strName << "戰斗序列" << endl;//需要調用所有的組合對象的操作vector::iterator pos;for( pos = m_pComponents.begin();posAssault();}}};
我們再來(lái)看一下,客戶(hù)端如何來(lái)調用該對象:
int main(int argc, char* argv[]){GeneralComposite pArmy("梁山大軍");GeneralComposite* pHorseArmy = new GeneralComposite("梁山馬軍");GeneralComposite* pPaceArmy = new GeneralComposite("梁山步軍");General *pWusong = new General("好漢武松");General *pHuaheshang = new General("俠客魯智深");General *pLida = new General("莽漢李逵");General *pLinchong = new General("英雄林沖");General *pGuansheng = new General("大刀關(guān)勝");pHorseArmy->Add(pLinchong);pHorseArmy->Add(pGuansheng);pPaceArmy->Add(pWusong);pPaceArmy->Add(pHuaheshang);pPaceArmy->Add(pLida);pArmy.Add(pHorseArmy);pArmy.Add(pPaceArmy);pArmy.Assault();return 0;} 運行結果如下:
我們可以看到,通過(guò)組合模式,對于對象的調用變得簡(jiǎn)單了起來(lái),只要通過(guò)一個(gè)函數就可以實(shí)現對所有對象的統一調用。這樣做最大的好處就是將所有的代碼統一成了一份,可以避免在不同的地方出現類(lèi)似的代碼。在多人合作的情況下,由于交流不充分的問(wèn)題,很多時(shí)候開(kāi)發(fā)人員各自寫(xiě)各自的代碼,而不關(guān)心別人的代碼,極容易產(chǎn)生相同功能的代碼。當我們的類(lèi)進(jìn)行擴展的時(shí)候,可能很多地方都需要進(jìn)行修改。
我們再來(lái)看看組合模式可能存在的問(wèn)題。Leaf類(lèi)繼承了Component中的所有方法,但對于A(yíng)dd,Remove,GetChild等操作,這些操作對它來(lái)說(shuō)是無(wú)意義的。我們也可以把這些操作放到Composite中,但在這種情況下,我們將無(wú)法通過(guò)基類(lèi)來(lái)直接操作Composite對象。個(gè)人覺(jué)得這個(gè)問(wèn)題可以根據具體問(wèn)題權衡解決。我們也可以為Component擴展更多的Leaf或者Composite,在這種情況下,如果我們想控制Composite中只能添加限定的Leaf或者Composite在靜態(tài)情況下是不可能的,必須在運行時(shí)刻去判斷。
還有,我們應該最大化Component接口,我們使用該接口的目的就是使用戶(hù)不知道它具體操作的是哪個(gè)Leaf或者Composite,所以我們應該盡量多定義一些它們公共的操作。這樣還是會(huì )造成我們上面提到的會(huì )給一些類(lèi)帶來(lái)無(wú)意義的功能。
我們再來(lái)簡(jiǎn)單比較一下Composite和Decorator模式,這兩個(gè)模式都會(huì )有一個(gè)子類(lèi)擁有基類(lèi)的指針,不同之處是Composite會(huì )擁有多個(gè)組件,而Decorator只擁有一個(gè)組件。Composite主要目的是對象的聚集,使它們的調用統一化;而Decorator不是此目的,它的目的是給對象添加一些額外的功能。
好了,Composite的講解先到此為止。如果你感興趣的話(huà),可以試著(zhù)把我們第一章用到的策略模式應用到該處。不過(guò)我們下面看另外一個(gè)問(wèn)題。在main中,我們需要很多代碼去完成對象的創(chuàng )建。我們是否可以將對象的創(chuàng )建部分也做一些封裝呢?我們可以找找創(chuàng )建型的模式中是否有適合我們的模式?
在《設計模式》中,我們可以找到Builder模式,它的定義是:將一個(gè)復雜對象的構建與它的表示分離,使得同樣的構建過(guò)程可以創(chuàng )建不同的表示。和我們的意圖有點(diǎn)象,先來(lái)看看《設計模式》中為我們提供的類(lèi)圖:
作一下解釋?zhuān)?
1, Director是具體要用到復雜對象的部分(如GeneralComponent)的類(lèi)(我們程序中是main中調用)。它會(huì )通過(guò)Builder來(lái)建造自己需要的對象,而不需要自己通過(guò)new來(lái)得到所要的對象。
2, 而B(niǎo)uilder的是一個(gè)抽象的創(chuàng )建對象的接口。
3, ConcreteBuilder是我們用來(lái)具體創(chuàng )建對象的類(lèi)。它可能有多個(gè),用來(lái)幫我們建造不同類(lèi)型的對象。如我們把我們的General派生出不同的風(fēng)格,如先秦風(fēng)格和羅馬風(fēng)格的時(shí)候,我們可能需要生成不同的類(lèi)。而每次創(chuàng )建一種風(fēng)格的對象,我們就可以擴展Builder而生成不同的創(chuàng )建類(lèi)。
我們的例子比較簡(jiǎn)單,不需要這么多東西。Builder的重要特征是:它是用來(lái)一步一步創(chuàng )建復雜對象類(lèi)型的。我們的組合類(lèi)由于需要組合不同的組件,所以整個(gè)創(chuàng )建過(guò)程比較復雜,那么可以借用Builder模式的特點(diǎn)來(lái)創(chuàng )建我們的對象。好的,看看具體的代碼:
class Builder{public:static GeneralComponent* CreateCompise(GeneralComponent *pParent,string strArr[],int iLength = 1){if ( pParent != NULL ){for( int i = 0 ; i < iLength ; i++ ){pParent->Add(new GeneralComposite(strArr[i]));}return pParent;}else{return new GeneralComposite(strArr[0]);}}static GeneralComponent* CreateGeneral(GeneralComponent *pParent,string strArr[],int iLength = 1){if ( pParent != NULL ){for( int i = 0 ; i < iLength ; i++ ){pParent->Add(new General(strArr[i]));}return pParent;}else{return new General(strArr[0]);}}}; 這邊的代碼略顯簡(jiǎn)單,只重意了,你可以看到,創(chuàng )建過(guò)程被封裝成CreateCompise和CreateGeneral兩個(gè)方法,通過(guò)它們來(lái)創(chuàng )建我們所需要的對象。只要將所需要的Composite(為它創(chuàng )建Leaf和子Composite的),還有創(chuàng )建對象所需要的參數(例子有些簡(jiǎn)單,只要一個(gè)初始化參數)數組和數組長(cháng)度傳進(jìn)函數就可以幫我們創(chuàng )建所需要的對象了。由于Builder沒(méi)有成員變量,將兩個(gè)函數設置成了靜態(tài)函數,想當于一個(gè)單件類(lèi)型。我們再看看它的具體用法:
int main(int argc, char* argv[]){string strArmy[1] = {"梁山大軍"};string strArmyChild[2] = {"梁山馬軍","梁山步軍"};string strSpecLeader[3] = {"好漢武松","俠客魯智深","莽漢李逵"};string strHorseLeader[2] = {"英雄林沖","大刀關(guān)勝"};GeneralComponent *pArmy = Builder::CreateCompise(NULL,strArmy);Builder::CreateCompise(pArmy,strArmyChild,2);Builder::CreateGeneral(pArmy->GetChild(0),strHorseLeader,2);Builder::CreateGeneral(pArmy->GetChild(1),strSpecLeader,3);pArmy->Assault();delete pArmy;} 這樣我們就可以統一對象的創(chuàng )建過(guò)程。做到一定程度的聚合。我個(gè)人感覺(jué)模式的學(xué)習過(guò)程是從形到意(就是從一些具體的例子看起,通過(guò)具體的例子來(lái)理解模式);而后是重意不重形(明白模式的意義之后,我們就可以隨意使用模式了,而不需要硬套公式)。所以此處使用了和書(shū)上并不一致的Builder模型。其實(shí)Builder模式與Abstract Factory模式很相似,我們下次描述Abstract Factory模式的時(shí)候,再回頭比較一下。
參考書(shū)目:1, 設計模式——可復用面向對象軟件的基礎(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著(zhù) 李英軍等譯 機械工業(yè)出版社2, Head First Design Patterns(影印版)Freeman等著(zhù) 東南大學(xué)出版社3, 道法自然——面向對象實(shí)踐指南 王詠武 王詠剛著(zhù) 電子工業(yè)出版社4, 水滸傳 —— 網(wǎng)上找到的電子檔