简介
对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行。(递归调用)
由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。
组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象,这就是组合模式的模式动机。
模式定义
组合模式(Composite Pattern):组合多个对象形成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
组合模式又可以称为“整体-部分”(Part-Whole)模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系。
组合模式结构
- 组件(Component):为组合中的对象声明接口,声明了类共有接口的缺省行为(如这里的Add,Remove,GetChild函数),声明一个接口函数可以访问Component的子组件。
1). Component::ComponentPtr:定义了各个组件共有的行为接口,由各个组件的具体实现.
2). Component::add添加一个子组件
3). Component::remove::删除一个子组件.
4). Component::display:获得子组件的指针. - 容器(Composite):是含有子组件的类,其内部包含了多种方法,主要是在当中实现了add与remove这类的操作用于对子组件进行控制。
- 叶节点(Leaf):是叶子结点,也就是不含有子组件的结点。
代码示例
一个集团公司,它有一个母公司,下设很多家子公司。不管是母公司还是子公司,都有各自直属的财务部、人力资源部、销售部等。对于母公司来说,不论是子公司,还是直属的财务部、人力资源部,都是它的部门。整个公司的部门拓扑图就是一个树形结构。
下面给出组合模式的UML图。从图中可以看到,FinanceDepartment、HRDepartment两个类作为叶结点,因此没有定义添加函数。而ConcreteCompany类可以作为中间结点,所以可以有添加函数。那么怎么添加呢?这个类中定义了一个链表,用来放添加的元素。
#include <iostream>
#include <list>
#include <memory>
#include <utility>
#include <cstddef>
using namespace std;
class Company
{
public:
Company(string name) { m_name = name; }
virtual ~Company() {}
virtual void Add(std::unique_ptr<Company>) {}//这里默认实现为空函数,leaf节点中不用实现为空了
virtual void Show(int depth) {} // 这里默认实现为空函数,leaf节点中不用实现为空了
protected:
string m_name;
};
//具体公司
class ConcreteCompany : public Company
{
public:
ConcreteCompany(string name) : Company(name) {}
virtual ~ConcreteCompany()
{
for (auto& company : m_listCompany)
{
company.reset(nullptr);
}
}
void Add(std::unique_ptr<Company> pCom) { m_listCompany.push_back(std::move(pCom)); } //位于树的中间,可以增加子树
void Show(int depth)
{
for (int i = 0; i < depth; i++)
cout << "-";
cout << m_name << endl;
auto iter = m_listCompany.begin();
for (; iter != m_listCompany.end(); iter++) //显示下层结点
(*iter)->Show(depth + 2);
}
private:
list<std::unique_ptr<Company>> m_listCompany;
};
//具体的部门,财务部
class FinanceDepartment : public Company
{
public:
FinanceDepartment(string name) :Company(name) {}
virtual ~FinanceDepartment() {}
virtual void Show(int depth) //只需显示,无限添加函数,因为已是叶结点
{
for (int i = 0; i < depth; i++)
cout << "-";
cout << m_name << endl;
}
};
//具体的部门,人力资源部
class HRDepartment :public Company
{
public:
HRDepartment(string name) :Company(name) {}
virtual ~HRDepartment() {}
virtual void Show(int depth) //只需显示,无限添加函数,因为已是叶结点
{
for (int i = 0; i < depth; i++)
cout << "-";
cout << m_name << endl;
}
};
int main()
{
auto root = std::make_unique<ConcreteCompany>("总公司");
auto leaf1 = std::make_unique < FinanceDepartment>("总公司财务部");
auto leaf2 = std::make_unique < HRDepartment>("总公司人力资源部");
root->Add(std::move(leaf1));
root->Add(std::move(leaf2));
//分公司
auto mid1 = std::make_unique < ConcreteCompany>("杭州分公司");
auto leaf3 = std::make_unique < FinanceDepartment>("杭州分公司财务部");
auto leaf4 = std::make_unique < HRDepartment>("杭州分公司人力资源部");
mid1->Add(std::move(leaf3));
mid1->Add(std::move(leaf4));
root->Add(std::move(mid1));
//分公司
auto mid2 = std::make_unique < ConcreteCompany>("上海分公司");
auto leaf5 = std::make_unique < FinanceDepartment>("上海分公司财务部");
auto leaf6 = std::make_unique < HRDepartment>("上海分公司人力资源部");
mid2->Add(std::move(leaf5));
mid2->Add(std::move(leaf6));
root->Add(std::move(mid2));
root->Show(0);
return 0;
}
组合模式优缺点
组合和我们之前讲的装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。
大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。