组合模式是一种非常有用的设计模式,用于解决**"部分-整体"**问题。它允许我们用树形结构来表示对象的层次结构,并且让客户端可以统一地操作单个对象和组合对象。
组合模式的核心思想
什么是组合模式?
组合模式的目的是将对象组织成树形结构,表示"部分-整体"的层次结构,使客户端能够以一致的方式处理单个对象和组合对象。
组合模式的结构
组合模式一般由以下三个部分组成:
- 组件(Component):定义了组合对象和叶子对象的通用接口。
- 叶子节点(Leaf):表示树的基本单元,不能再包含其他子对象。
- 组合节点(Composite):表示有子节点的对象,它实现了组件接口并且可以操作其子节点。
组合模式的特点
- 递归结构:树形结构的每个节点既可以是叶子,也可以是子树。
- 统一接口:客户端通过相同的接口处理单个对象和组合对象。
案例:公司组织架构
案例背景
我们需要设计一个程序来模拟公司组织架构:
- 公司有不同的部门(如总部、人力资源部、技术部等)。
- 每个部门可以包含其他部门和员工。
- 我们希望能够清晰地输出公司组织的层次结构。
代码分块及讲解
1. 抽象组件类
这是组合模式的核心部分,所有叶子节点和组合节点都必须实现这个抽象类。通过这个类,我们可以定义统一的操作接口。
cpp
// 抽象组件类: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. 叶子节点类
叶子节点是树形结构的最底层元素,它不包含任何子节点。比如在组织架构中,员工就是叶子节点。
cpp
// 叶子节点类: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. 组合节点类
组合节点可以包含其他节点(叶子节点或组合节点),并负责管理它们。比如一个部门可以包含其他部门或员工。
cpp
// 组合节点类: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. 客户端代码
在客户端代码中,我们将创建叶子节点(员工)和组合节点(部门),并将它们组织成树形结构,最后输出公司组织架构。
cpp
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组件树等场景。
源码分享
cpp
#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多平台发布