组合模式是一种非常有用的设计模式,用于解决**“部分-整体”**问题。它允许我们用树形结构来表示对象的层次结构,并且让客户端可以统一地操作单个对象和组合对象。
组合模式的核心思想
什么是组合模式?
组合模式的目的是将对象组织成树形结构,表示“部分-整体”的层次结构,使客户端能够以一致的方式处理单个对象和组合对象。
组合模式的结构
组合模式一般由以下三个部分组成:
- 组件(Component):定义了组合对象和叶子对象的通用接口。
- 叶子节点(Leaf):表示树的基本单元,不能再包含其他子对象。
- 组合节点(Composite):表示有子节点的对象,它实现了组件接口并且可以操作其子节点。
组合模式的特点
- 递归结构:树形结构的每个节点既可以是叶子,也可以是子树。
- 统一接口:客户端通过相同的接口处理单个对象和组合对象。
案例:公司组织架构
案例背景
我们需要设计一个程序来模拟公司组织架构:
- 公司有不同的部门(如总部、人力资源部、技术部等)。
- 每个部门可以包含其他部门和员工。
- 我们希望能够清晰地输出公司组织的层次结构。
代码分块及讲解
1. 抽象组件类
这是组合模式的核心部分,所有叶子节点和组合节点都必须实现这个抽象类。通过这个类,我们可以定义统一的操作接口。
// 抽象组件类:Component
class Component {
public:// 展示信息的接口,所有子类都需要实现virtual void display(int depth) const = 0;// 以下两个接口仅供组合节点使用,叶子节点不需要实现virtual void add(std::shared_ptr<Component> component) {}virtual void remove(std::shared_ptr<Component> component) {}// 虚析构函数,保证子类正确释放资源virtual ~Component() = default;
};
分析:
display(int depth)
:定义了展示对象信息的方法,depth
用于控制输出的层次缩进。add
和remove
:用于操作子节点,默认为空实现,叶子节点无需实现这两个方法。- 虚析构函数:确保使用多态时,子类可以正确释放资源。
2. 叶子节点类
叶子节点是树形结构的最底层元素,它不包含任何子节点。比如在组织架构中,员工就是叶子节点。
// 叶子节点类:Employee(员工)
class Employee : public Component {
private:std::string name; // 员工姓名public:explicit Employee(const std::string& name) : name(name) {}// 实现展示接口void display(int depth) const override {std::cout << std::string(depth, '-') << "员工: " << name << std::endl;}
};
分析:
name
:记录员工姓名。display
:实现了抽象类的接口,输出员工的信息,并用depth
控制层次缩进。- 叶子节点没有子节点:因此不需要实现
add
和remove
方法。
3. 组合节点类
组合节点可以包含其他节点(叶子节点或组合节点),并负责管理它们。比如一个部门可以包含其他部门或员工。
// 组合节点类:Department(部门)
class Department : public Component {
private:std::string name; // 部门名称std::vector<std::shared_ptr<Component>> children; // 子节点集合public:explicit Department(const std::string& name) : name(name) {}// 添加子节点void add(std::shared_ptr<Component> component) override {children.push_back(component);}// 移除子节点void remove(std::shared_ptr<Component> component) override {children.erase(std::remove(children.begin(), children.end(), component),children.end());}// 展示部门及其子节点信息void display(int depth) const override {std::cout << std::string(depth, '-') << "部门: " << name << std::endl;// 遍历并递归调用子节点的display方法for (const auto& child : children) {child->display(depth + 2); // 子节点缩进}}
};
分析:
name
:记录部门名称。children
:用一个vector
存储部门的子节点(可以是员工或其他部门)。add
和remove
:用于动态添加或移除子节点。display
:- 先输出当前部门的信息。
- 然后递归调用所有子节点的
display
方法,以树形方式展示整个结构。
4. 客户端代码
在客户端代码中,我们将创建叶子节点(员工)和组合节点(部门),并将它们组织成树形结构,最后输出公司组织架构。
int main() {// 创建叶子节点(员工)auto emp1 = std::make_shared<Employee>("张三");auto emp2 = std::make_shared<Employee>("李四");auto emp3 = std::make_shared<Employee>("王五");// 创建组合节点(部门)auto department1 = std::make_shared<Department>("人力资源部");auto department2 = std::make_shared<Department>("技术部");// 给部门添加员工department1->add(emp1);department2->add(emp2);department2->add(emp3);// 创建公司根节点auto company = std::make_shared<Department>("总部");// 将部门加入公司company->add(department1);company->add(department2);// 展示公司组织架构company->display(0);return 0;
}
分析:
- 创建叶子节点(员工):
- 创建三个员工对象,分别为“张三”、“李四”和“王五”。
- 创建组合节点(部门):
- 创建两个部门,分别为“人力资源部”和“技术部”。
- 构建树结构:
- 将“张三”加入人力资源部。
- 将“李四”和“王五”加入技术部。
- 将两个部门加入公司的根节点“总部”。
- 输出组织架构:
- 调用
display
方法,从根节点开始递归输出整棵树的结构。
- 调用
运行结果
运行程序后,输出如下:
部门: 总部
--部门: 人力资源部
----员工: 张三
--部门: 技术部
----员工: 李四
----员工: 王五
输出说明:
- 根节点“总部”包含两个子节点:人力资源部和技术部。
- 每个部门又包含各自的员工信息,清晰地展示了整个公司组织结构。
组合模式的优缺点
优点
- 统一性:客户端代码可以统一处理单个对象和组合对象,简化了逻辑。
- 扩展性:可以轻松增加新的叶子节点或组合节点。
- 灵活性:支持动态调整树形结构(如添加、删除节点)。
缺点
- 复杂性:对于简单结构来说,使用组合模式会增加不必要的复杂性。
- 通用性限制:某些特定操作可能只适合叶子节点,但为了统一接口,组合节点也必须实现这些方法。
总结
组合模式通过将单个对象和组合对象统一处理,解决了“部分-整体”问题,特别适合树形结构的数据处理场景。通过案例代码我们可以看到:
- 使用组合模式可以清晰地表示公司组织架构。
- 递归调用的
display
方法是组合模式的核心,能够清晰输出树形层次结构。 - 组合模式广泛应用于组织架构、文件系统、GUI组件树等场景。
源码分享
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <algorithm> // 用于删除元素// 抽象组件类:Component
class Component {
public:// 展示信息的接口,所有子类都需要实现virtual void display(int depth) const = 0;// 以下两个接口仅供组合节点使用,叶子节点不需要实现virtual void add(std::shared_ptr<Component> component) {}virtual void remove(std::shared_ptr<Component> component) {}// 虚析构函数,保证子类正确释放资源virtual ~Component() = default;
};// 叶子节点类:Employee(员工)
class Employee : public Component {
private:std::string name; // 员工姓名public:explicit Employee(const std::string& name) : name(name) {}// 实现展示接口void display(int depth) const override {// 使用深度(depth)控制输出的缩进层次std::cout << std::string(depth, '-') << "员工: " << name << std::endl;}
};// 组合节点类:Department(部门)
class Department : public Component {
private:std::string name; // 部门名称std::vector<std::shared_ptr<Component>> children; // 子节点集合public:explicit Department(const std::string& name) : name(name) {}// 添加子节点void add(std::shared_ptr<Component> component) override {children.push_back(component);}// 移除子节点void remove(std::shared_ptr<Component> component) override {children.erase(std::remove(children.begin(), children.end(), component),children.end());}// 展示部门及其子节点信息void display(int depth) const override {// 输出当前部门信息std::cout << std::string(depth, '-') << "部门: " << name << std::endl;// 遍历所有子节点,并递归调用它们的display方法for (const auto& child : children) {child->display(depth + 2); // 子节点缩进更深}}
};// 客户端代码
int main() {// 创建叶子节点(员工)auto emp1 = std::make_shared<Employee>("张三"); // 人力资源部的员工auto emp2 = std::make_shared<Employee>("李四"); // 技术部的员工auto emp3 = std::make_shared<Employee>("王五"); // 技术部的员工// 创建组合节点(部门)auto department1 = std::make_shared<Department>("人力资源部");auto department2 = std::make_shared<Department>("技术部");// 给部门添加员工department1->add(emp1); // 添加张三到人力资源部department2->add(emp2); // 添加李四到技术部department2->add(emp3); // 添加王五到技术部// 创建公司根节点auto company = std::make_shared<Department>("总部");// 将部门加入公司company->add(department1); // 将人力资源部加入总部company->add(department2); // 将技术部加入总部// 展示公司组织架构company->display(0); // 从根节点开始展示整个组织架构return 0;
}
运行结果
运行此代码,程序将输出如下内容:
部门: 总部
--部门: 人力资源部
----员工: 张三
--部门: 技术部
----员工: 李四
----员工: 王五
本文由mdnice多平台发布