1.定义
组合模式是一种设计模式,它允许将对象组合成树形结构来表示 “部分 - 整体” 的层次关系,使得客户端可以统一地处理单个对象和对象组合,而无需区分它们。
2. 结构组成
- 组件(Component):
- 定义:是组合模式中的抽象类或接口,它定义了组合对象和叶子对象的公共操作,为客户端提供统一的操作接口。
- 常见方法:
- 操作方法:例如execute()或operation()等,在不同的实现类中会有不同的具体操作逻辑。
- 管理子组件的方法:如add(Component component)用于添加子组件,remove(Component component)用于移除子组件。这些方法在叶子类中可能为空实现。
- 判断是否为组合对象的方法:如isComposite()用于判断当前对象是否是组合对象。
- 叶子(Leaf):
- 定义:是组合模式中的基本对象,它没有子对象,实现了组件接口中定义的方法,完成具体的操作。
- 特点:
- 叶子对象是树形结构中的叶子节点,是操作的实际执行者。
- 由于没有子对象,其添加和移除子对象的方法通常是空的。
- 组合(Composite):
- 定义:是包含子对象的对象,它可以是其他组合对象或叶子对象。它实现了组件接口中的方法,并且在这些方法中调用其子对象的相应方法。
- 内部结构:
- 通常包含一个数据结构(如列表、数组等)来存储子组件,例如children。
- 实现管理子组件的方法,如add和remove方法会操作内部的子组件存储结构。
- 在操作方法(如execute)中,会遍历子组件并调用它们的操作方法,实现将操作委派给子组件的功能。
- 客户端(Client):
- 作用:是使用组合模式的代码部分,通过组件接口来操作组合对象和叶子对象,无需知道它们的具体类型。
- 操作方式:
- 客户端调用组件的操作方法,由具体的对象(叶子或组合)来执行实际的操作。
- 例如,客户端可以调用component.operation(),而不需要关心component是叶子对象还是组合对象。
3. 组合模式结构
组件 (Component) 接口描述了树中简单项目和复杂项目所共有的操作。
叶节点 (Leaf) 是树的基本结构, 它不包含子项目。
一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。
容器 (Container)——又名 “组合 (Composite)”——是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互。
容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。
客户端 (Client) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。
4. 示例代码
#include <iostream>
#include <memory>
#include <string>
#include <list>
using namespace std;
class Component{
public:
virtual ~Component(){};
shared_ptr<Component> _parent;
shared_ptr<Component> GetParent(){ return this->_parent; }
void SetParent(shared_ptr<Component> parent){ this->_parent = parent; }
virtual void Add(shared_ptr<Component> c){cout << "default Add" <<endl;}
virtual void Remove(shared_ptr<Component> c){cout << "default Remove" <<endl;}
virtual bool IsComposite(){return false;}
virtual string excute()const = 0;
};
class Leaf : public Component{
public:
string excute()const override{ return "Leaf";}
};
class Composite : public Component ,public enable_shared_from_this<Composite>{
protected:
list<shared_ptr<Component>> Children;
public:
void Add(shared_ptr<Component> c){
Children.push_back(c);
c->SetParent(shared_from_this());
}
void Remove(shared_ptr<Component> c){
Children.remove(c);
c->SetParent(nullptr);
}
bool IsComposite(){return true;}
string excute()const{
string result;
for(const shared_ptr<Component> c : Children){
if(c == Children.back()){
result += c->excute();
}else{
result += c->excute() + "+";
}
}
return "Branch(" + result + ")";
}
};
void client(shared_ptr<Component> c){
cout<< c->excute()<<endl;
}
int main(){
shared_ptr<Leaf> L1 = make_shared<Leaf>();
client(L1);
cout<< "\n \n";
shared_ptr<Leaf> L2 = make_shared<Leaf>();
shared_ptr<Composite> C1 = make_shared<Composite>();
C1->Add(L1);
C1->Add(L2);
client(C1);
shared_ptr<Leaf> L3 = make_shared<Leaf>();
shared_ptr<Composite> C2 = make_shared<Composite>();
C2->Add(L1);
C2->Add(C1);
C2->Add(L2);
C2->Add(L3);
client(C2);
return 0;
}
5. 模式优势
- 简化客户端代码
- 客户端以统一的方式处理单个对象和组合对象,无需区分它们的具体类型,降低了客户端代码的复杂性。
- 易于扩展
- 可以方便地添加新类型的组件(叶子或组合),只要实现了组件接口,就可以无缝地集成到现有的系统中,符合开闭原则。
- 层次结构清晰
- 非常适合表示具有层次关系的数据结构,例如文件系统(文件和文件夹)、图形系统(简单图形和复合图形)等,使系统的层次结构更加清晰和易于理解。
- 代码复用性高
- 叶子对象和组合对象都基于相同的组件接口,操作方法可以在不同场景下复用。
6. 应用场景
- 图形系统
- 在图形系统中,简单图形(如圆形、矩形)可以作为叶子对象,而由多个图形组成的复杂图形可以作为组合对象。客户端可以统一地对它们进行绘制、移动等操作。
- 文件系统
- 文件系统中的文件可以作为叶子对象,文件夹作为组合对象。操作系统的文件管理器(客户端)可以统一地操作文件和文件夹,如显示内容、复制、删除等操作。
- 组织结构
- 在企业或组织的结构表示中,员工可以作为叶子对象,部门作为组合对象。可以方便地对组织进行管理和操作,如统计员工数量、计算部门绩效等。