对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行。(递归调用)
由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。
组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象,这就是组合模式的模式动机。
一、模式定义
组合模式(Composite Pattern):组合多个对象形成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
组合模式又可以称为“整体-部分”(Part-Whole)模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系。
组合模式的分类
1) 安全组合模式:将管理子元素的方法定义在Composite类中。
2) 透明组合模式:将管理子元素的方法定义在Component接口中,这样Leaf类就需要对这些方法空实现。
适用性
• 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
• 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
• 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们。
模式优点:
• 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
• 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
• 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
• 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
缺点
• 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。
• 增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制。
二、ULM图
下图是透明组合模式的ulm图
组合模式包含如下角色:
• Component: 抽象构件
• Leaf: 叶子构件
• Composite: 容器构件
• Client: 客户类
三、实例
3.1 公司:
一个集团公司,它有一个母公司,下设很多家子公司。不管是母公司还是子公司,都有各自直属的财务部、人力资源部、销售部等。对于母公司来说,不论是子公司,还是直属的财务部、人力资源部,都是它的部门。整个公司的部门拓扑图就是一个树形结构。
下面给出组合模式的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;
}
运行结果如下:
3.2 水果盘
在水果盘(Plate)中有一些水果,如苹果(Apple)、香蕉(Banana)、梨子(Pear),当然大水果盘中还可以有小水果盘,现需要对盘中的水果进行遍历(吃),当然如果对一个水果盘执行“吃”方法,实际上就是吃其中的水果。使用组合模式模拟该场景 。
代码实现如下:
#include <iostream>
#include <list>
#include <memory>
#include <utility>
#include <cstddef>
//抽象构建类MyElement
class MyElement
{
public:
virtual void eat() = 0;
};
//叶子构件类Apple
class Apple : public MyElement
{
public:
void eat() {
std::cout << "吃苹果!" << std::endl;
}
};
//叶子构件类Banana
class Banana : public MyElement {
public:
void eat() {
std::cout << "吃香蕉!" << std::endl;
}
};
//叶子构件类Pear
class Pear : public MyElement
{
public:
void eat() {
std::cout << "吃梨子!" << std::endl;
}
};
//容器构建类Plate
class Plate : public MyElement {
public:
void add(std::unique_ptr<MyElement> element)
{
ls.push_back(std::move(element));
}
void remove(std::unique_ptr<MyElement> element)
{
ls.remove(std::move(element));
}
void eat()
{
for (auto& i : ls)
{
i->eat();
}
}
~Plate()
{
for (auto& i : ls)
{
i.reset(nullptr);
}
}
private:
std::list<std::unique_ptr<MyElement>> ls;
};
//client
int main(void)
{
auto obj1 = std::make_unique<Apple>();
auto obj2 = std::make_unique < Pear>();
auto plate1 = std::make_unique < Plate>();
plate1->add(std::move(obj1));
plate1->add(std::move(obj2));
auto obj3 = std::make_unique < Banana>();
auto obj4 = std::make_unique < Banana>();
auto plate2 = std::make_unique < Plate>();
plate2->add(std::move(obj3));
plate2->add(std::move(obj4));
auto obj5 = std::make_unique < Apple>();
auto plate3 = std::make_unique < Plate>();
plate3->add(std::move(plate1));
plate3->add(std::move(plate2));
plate3->add(std::move(obj5));
plate3->eat();
return 0;
}
运行结果如下:
其他应用场景:
(1) XML文档解析
(2) 操作系统中的目录结构是一个树形结构,因此在对文件和文件夹进行操作时可以应用组合模式,例如杀毒软件在查毒或杀毒时,既可以针对一个具体文件,也可以针对一个目录。如果是对目录查毒或杀毒,将递归处理目录中的每一个子目录和文件。
来源:CSDN
作者:秋云
链接:https://blog.csdn.net/janeqi1987/article/details/103949150