從定義中可以得到使用組合模式的環境爲:在設計中想表示對象的“部分-整體”層次結構;希望用戶忽略組合對象與單個對象的不同,統一地使用組合結構中的所有對象。
看下組合模式的組成。
1) 抽象構件角色Component:它爲組合中的對象聲明接口,也可以爲共有接口實現缺省行爲。
2) 樹葉構件角色Leaf:在組合中表示葉節點對象——沒有子節點,實現抽象構件角色聲明的接口。
3) 樹枝構件角色Composite:在組合中表示分支節點對象——有子節點,實現抽象構件角色聲明的接口;存儲子部件。
Component:
爲組合中的對象聲明接口;
在適當的情況下,實現所有類共有接口的缺省行爲;
聲明一個接口用於訪問和管理Component的子組件。
Leaf:
在組合中表示葉節點對象,葉節點沒有子節點;
在組合中定義葉節點的行爲。
Composite:
定義有子部件的那些部件的行爲;
存儲子部件。
Client:
通過Component接口操作組合部件的對象。
組合模式中必須提供對子對象的管理方法,不然無法完成對子對象的添加刪除等等操作,也就失去了靈活性和擴展性。但是管理方法是在Component中就聲明還是在Composite中聲明呢?
一種方式是在Component裏面聲明所有的用來管理子類對象的方法,以達到Component接口的最大化(如下圖所示)。目的就是爲了使客戶看來在接口層次上樹葉和分支沒有區別——透明性。但樹葉是不存在子類的,因此Component聲明的一些方法對於樹葉來說是不適用的。這樣也就帶來了一些安全性問題。
另一種方式就是隻在Composite裏面聲明所有的用來管理子類對象的方法(如下圖所示)。這樣就避免了上一種方式的安全性問題,但是由於葉子和分支有不同的接口,所以又失去了透明性。
《設計模式》一書認爲:在這一模式中,相對於安全性,我們比較強調透明性。對於第一種方式中葉子節點內不需要的方法可以使用空處理或者異常報告的方式來解決。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 抽象的部件類描述將來所有部件共有的行爲
class Component
{
public:
Component(string name) : m_strCompname(name){}
virtual ~Component(){}
virtual void Operation() = 0;
virtual void Add(Component *) = 0;
virtual void Remove(Component *) = 0;
virtual Component *GetChild(int) = 0;
virtual string GetName()
{
return m_strCompname;
}
virtual void Print() = 0;
protected:
string m_strCompname;
};
class Leaf : public Component
{
public:
Leaf(string name) : Component(name)
{}
void Operation()
{
cout<<"I'm "<<m_strCompname<<endl;
}
void Add(Component *pComponent){}
void Remove(Component *pComponent){}
Component *GetChild(int index)
{
return NULL;
}
void Print(){}
};
class Composite : public Component
{
public:
Composite(string name) : Component(name)
{}
~Composite()
{
vector<Component *>::iterator it = m_vecComp.begin();
while (it != m_vecComp.end())
{
if (*it != NULL)
{
cout<<"----delete "<<(*it)->GetName()<<"----"<<endl;
delete *it;
*it = NULL;
}
m_vecComp.erase(it);
it = m_vecComp.begin();
}
}
void Operation()
{
cout<<"I'm "<<m_strCompname<<endl;
}
void Add(Component *pComponent)
{
m_vecComp.push_back(pComponent);
}
void Remove(Component *pComponent)
{
for (vector<Component *>::iterator it = m_vecComp.begin(); it != m_vecComp.end(); ++it)
{
if ((*it)->GetName() == pComponent->GetName())
{
if (*it != NULL)
{
delete *it;
*it = NULL;
}
m_vecComp.erase(it);
break;
}
}
}
Component *GetChild(int index)
{
if (index > m_vecComp.size())
{
return NULL;
}
return m_vecComp[index - 1];
}
void Print()
{
for (vector<Component *>::iterator it = m_vecComp.begin(); it != m_vecComp.end(); ++it)
{
cout<<(*it)->GetName()<<endl;
}
}
private:
vector<Component *> m_vecComp;
};
int main(int argc, char *argv[])
{
Component *pNode = new Composite("Beijing Head Office");
Component *pNodeHr = new Leaf("Beijing Human Resources Department");
Component *pSubNodeSh = new Composite("Shanghai Branch");
Component *pSubNodeCd = new Composite("Chengdu Branch");
Component *pSubNodeBt = new Composite("Baotou Branch");
pNode->Add(pNodeHr);
pNode->Add(pSubNodeSh);
pNode->Add(pSubNodeCd);
pNode->Add(pSubNodeBt);
pNode->Print();
Component *pSubNodeShHr = new Leaf("Shanghai Human Resources Department");
Component *pSubNodeShCg = new Leaf("Shanghai Purchasing Department");
Component *pSubNodeShXs = new Leaf("Shanghai Sales department");
Component *pSubNodeShZb = new Leaf("Shanghai Quality supervision Department");
pSubNodeSh->Add(pSubNodeShHr);
pSubNodeSh->Add(pSubNodeShCg);
pSubNodeSh->Add(pSubNodeShXs);
pSubNodeSh->Add(pSubNodeShZb);
pNode->Print();
// 公司不景氣,需要關閉上海質量監督部門
pSubNodeSh->Remove(pSubNodeShZb);
if (pNode != NULL)
{
delete pNode;
pNode = NULL;
}
return 0;
}
Composite的關鍵之一在於一個抽象類,它既可以代表Leaf,又可以代表Composite;所以在實際實現時,應該最大化Component接口,Component類應爲Leaf和Composite類儘可能多定義一些公共操作。Component類通常爲這些操作提供缺省的實現,而Leaf和Composite子類可以對它們進行重定義;
Component是否應該實現一個Component列表,在上面的代碼中,我是在Composite中維護的列表,由於在Leaf中,不可能存在子Composite,所以在Composite中維護了一個Component列表,這樣就減少了內存的浪費;
內存的釋放;由於存在樹形結構,當父節點都被銷燬時,所有的子節點也必須被銷燬,所以,我是在析構函數中對維護的Component列表進行統一銷燬,這樣就可以免去客戶端頻繁銷燬子節點的困擾;
由於在Component接口提供了最大化的接口定義,導致一些操作對於Leaf節點來說並不適用,比如:Leaf節點並不能進行Add和Remove操作,由於Composite模式屏蔽了部分與整體的區別,爲了防止客戶對Leaf進行非法的Add和Remove操作,所以,在實際開發過程中,進行Add和Remove操作時,需要進行對應的判斷,判斷當前節點是否爲Composite。