1.动机
对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行。
由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。
组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象,这就是组合模式的模式动机。
2.定义
组合(Composite)模式将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式(Composite)将小对象组合成树形结构,使用户操作组合对象如同操作一个单个对象。组合模式定义了“部分-整体”的层次结构,基本对象可以被组合成更大的对象,而且这种操作是可重复的,不断重复下去就可以得到一个非常大的组合对象,但这些组合对象与基本对象拥有相同的接口,因而组合是透明的,用法完全一致。
组合模式就是把一些现有的对象或者元素,经过组合后组成新的对象,新的对象提供内部方法,可以让我们很方便的完成这些元素或者内部对象的访问和操作。我们也可以把组合对象理解成一个容器,容器提供各种访问其内部对象或者元素的API,我们只需要使用这些方法就可以操作它了。
3.结构
Component:
为组合中的对象声明接口;在适当的情况下,实现所有类共有接口的缺省行为;声明一个接口用于访问和管理Component的子组件。
Leaf:
在组合中表示叶节点对象,叶节点没有子节点;在组合中定义叶节点的行为。
Composite:
定义有子部件的那些部件的行为;存储子部件。
Client:
通过Component接口操作组合部件的对象。
4.代码分析
#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)
{
cout << "----delete " << (*it)->GetName() << "----" << endl;
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;
};
//静态类型是Componet,但是其动态类型可以是leaf,也可以是Composite
//从而形成树状关系
int main()
{
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();
cout << endl;
//成都分公司效益不好,需要将其关闭;
pNode->Remove(pSubNodeCd);
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();
cout << endl;
// 上海部门不景气,需要关闭上海质量监督部门;
pSubNodeSh->Remove(pSubNodeShZb);
if (pNode != NULL)
{
delete pNode;
pNode = NULL;
}
system("pause");
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。
5.优点
可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
6.缺点
使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。
增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制。
7.适用场景
你想表示对象的部分-整体层次结构;
希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。