📕目录
五、C++ 实战示例 3:项目级菜单系统(组合模式 + 迭代器)

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
前言
开篇先抛一个真实开发场景:假设你要开发一个文件管理工具,需要支持 “显示文件结构”“计算占用空间”“删除文件 / 文件夹” 三个核心功能。文件系统的结构很明确 —— 文件夹里可以包含文件或子文件夹,子文件夹里又能嵌套更多文件和文件夹,形成典型的树形结构。

如果用传统方式设计,你需要单独写逻辑处理 “文件”(单个对象)和 “文件夹”(组合对象):
- 显示结构时,要判断当前是文件还是文件夹,文件夹还得递归遍历子元素;
- 计算占用空间时,文件直接返回自身大小,文件夹要累加所有子文件和子文件夹的大小;
- 删除时,文件直接删除,文件夹要先删除所有子元素再删除自身。
这样写出来的代码会充满if-else判断,冗余且难以维护 —— 新增 “移动”“复制” 功能时,又要重复写一遍判断逻辑;如果后续扩展 “压缩包”(既是文件又是容器),整个代码架构都要重构。
而用组合模式设计,只需定义一个统一接口,让文件(叶子)和文件夹(容器)都实现这个接口,客户端无需区分 “单个对象” 和 “组合对象”,直接调用统一接口就能完成操作。比如显示结构时,不管是文件还是文件夹,都调用show()方法;计算大小都调用getSize()方法,极大简化代码,扩展也更灵活。
这篇文章我会用最通俗的语言,结合 3 个递进式 C++ 实战示例(从基础到项目级),从痛点分析、原理拆解、代码实现到实际应用,全方位讲解组合模式。无论你是刚学设计模式的新手,还是想在项目中落地的开发者,都能看懂、会用,文章总字数超 3000 字,干货满满,建议收藏慢慢看。
一、先搞懂:为什么需要组合模式?(从实际痛点出发)

在讲组合模式的定义之前,我们先通过两个真实开发场景,理解它要解决的核心问题 ——统一 “单个对象” 和 “组合对象” 的操作接口,简化树形结构的处理逻辑。
场景 1:文件系统的 “双重逻辑” 困境
如开篇所述,文件系统的核心矛盾是 “单个对象(文件)” 和 “组合对象(文件夹)” 的操作逻辑不统一:
- 操作维度:显示结构、计算大小、删除、复制等,每个操作都要区分对象类型;
- 扩展难题:新增操作(如移动、搜索)时,需要在所有判断逻辑中添加新代码;
- 嵌套复杂度:文件夹可以无限嵌套子文件夹,传统递归逻辑写起来繁琐且容易出错。
场景 2:组织架构的 “层级统计” 难题
假设你要开发一个员工管理系统,公司组织架构是 “部门包含员工和子部门”:
- 统计薪资时,员工直接返回自身薪资,部门要累加所有员工和子部门的总薪资;
- 打印组织架构时,员工直接显示姓名和职位,部门要递归显示所有子元素的层级关系;
- 新增 “部门调整” 功能时,又要单独处理员工和部门的不同逻辑。
传统设计的代码会像这样(伪代码):
// 传统设计伪代码(冗余且难维护)
void showStructure(Object obj) {
if (obj是文件) {
打印文件名;
} else if (obj是文件夹) {
打印文件夹名;
遍历所有子元素,调用showStructure(子元素);
}
}
int calculateSize(Object obj) {
if (obj是文件) {
return obj.size;
} else if (obj是文件夹) {
int total = 0;
遍历所有子元素,total += calculateSize(子元素);
return total;
}
}
这种代码的问题很明显:
- 冗余重复:每个操作都要写一遍 “判断对象类型” 的逻辑;
- 耦合度高:客户端需要知道 “文件” 和 “文件夹” 的区别,违反 “依赖倒置原则”;
- 扩展困难:新增对象类型(如压缩包)或操作(如搜索)时,需要修改所有相关逻辑,违反 “开闭原则”。
核心痛点总结
这两个场景的共性是 “存在树形结构,且需要对‘单个对象’和‘组合对象’执行相同操作”。传统设计的核心问题是 “接口不统一”,而组合模式的出现,正是为了解决这个问题 —— 通过定义统一接口,让客户端 “对单个对象和组合对象一视同仁”,无需关心内部结构。
二、组合模式核心原理:3 个角色 + 1 个核心思想
1. 核心思想
组合模式(Composite Pattern)的官方定义是:将对象组合成树形结构以表示 “部分 - 整体” 的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
通俗理解:把 “单个对象”(叶子)和 “组合对象”(容器)都看作 “构件”,给它们定义一个统一的接口,容器内部维护子构件(可以是叶子或其他容器),并实现接口的 “批量处理逻辑”(如递归遍历子构件)。客户端只需调用这个统一接口,无需区分是叶子还是容器。
2. 3 个核心角色
组合模式的结构很清晰,主要包含 3 个角色,我们结合 “文件系统” 场景来理解:
(1)抽象构件(Component)
- 定义 “单个对象” 和 “组合对象” 的统一接口,包含所有操作的声明(如显示结构、计算大小、添加、删除等);
- 可以是抽象类或接口,通常会提供默认实现(如添加、删除操作的默认实现为 “不支持”,避免叶子类重复实现)。
- 示例:
FileSystemComponent类,声明show()、getSize()、add()、remove()等方法。
(2)叶子构件(Leaf)
- 表示树形结构中的 “单个对象”,没有子构件;
- 实现抽象构件的接口,其中 “添加”“删除” 等操作通常不支持(抛出异常或返回错误)。
- 示例:
File类,实现show()(显示文件名)、getSize()(返回文件大小),add()和remove()抛出 “不支持该操作” 的提示。
(3)容器构件(Composite)
- 表示树形结构中的 “组合对象”,可以包含子构件(叶子或其他容器);
- 实现抽象构件的接口,其中 “添加”“删除” 操作用于管理子构件,其他操作(如显示、计算大小)会递归调用子构件的对应方法。
- 示例:
Folder类,维护一个vector<FileSystemComponent*>存储子构件,show()递归显示所有子构件,getSize()累加所有子构件的大小。
3. 结构示意图(文件系统场景)
抽象构件(FileSystemComponent)
├── 叶子构件(File):实现统一接口(无子构件)
└── 容器构件(Folder):实现统一接口(维护子构件,递归处理)
├── 叶子构件(File)
└── 容器构件(Folder)
└── 叶子构件(File)
通过这个结构,客户端可以:
- 直接调用
File的getSize()获取单个文件大小; - 调用
Folder的getSize(),自动累加所有子文件和子文件夹的大小; - 无需区分是
File还是Folder,统一调用show()就能显示结构。
三、C++ 实战示例 1:基础版文件系统(入门必看)

我们先通过最基础的文件系统场景,实现组合模式的核心逻辑 —— 统一文件(叶子)和文件夹(容器)的操作接口,支持显示结构、计算大小、添加 / 删除子元素。
1. 实现步骤拆解
- 定义抽象构件(
FileSystemComponent):声明统一接口(show()、getSize()、add()、remove()); - 实现叶子构件(
File):实现show()和getSize(),add()和remove()抛出不支持提示; - 实现容器构件(
Folder):维护子构件列表,实现所有接口,其中show()和getSize()递归处理子构件; - 客户端调用:创建文件和文件夹对象,组合成树形结构,调用统一接口完成操作。
2. 完整 C++ 代码(可直接运行)
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// --------------- 抽象构件(FileSystemComponent)---------------
// 文件系统抽象构件:定义文件和文件夹的统一接口
class FileSystemComponent {
protected:
string name_; // 文件名/文件夹名
public:
FileSystemComponent(const string& name) : name_(name) {}
virtual ~FileSystemComponent() {}
// 统一接口1:显示当前构件的结构(带层级缩进)
virtual void show(int depth = 0) const = 0;
// 统一接口2:获取当前构件的大小(文件:自身大小;文件夹:所有子构件大小之和)
virtual int getSize() const = 0;
// 统一接口3:添加子构件(文件不支持,文件夹支持)
virtual void add(FileSystemComponent* component) {
// 默认实现:抛出不支持该操作的提示
cout << "错误:" << name_ << " 不支持添加操作(不是文件夹)" << endl;
}
// 统一接口4:删除子构件(文件不支持,文件夹支持)
virtual void remove(FileSystemComponent* component) {
cout << "错误:" << name_ << " 不支持删除操作(不是文件夹)" << endl;
}
};
// --------------- 叶子构件(File)---------------
// 文件类:叶子构件,无子构件
class File : public FileSystemComponent {
private:
int size_; // 文件大小(单位:KB)
public:
// 构造函数:传入文件名和大小
File(const string& name, int size) : FileSystemComponent(name), size_(size) {}
// 实现show接口:显示文件名(带层级缩进)
void show(int depth = 0) const override {
// 缩进:depth为0无缩进,每级子目录增加2个空格
string indent(depth * 2, ' ');
cout << indent << "- 文件:" << name_ << "(大小:" << size_ << "KB)" << endl;
}
// 实现getSize接口:返回文件自身大小
int getSize() const override {
return size_;
}
// 无需重写add和remove,使用父类默认实现(不支持)
};
// --------------- 容器构件(Folder)---------------
// 文件夹类:容器构件,维护子构件列表
class Folder : public FileSystemComponent {
private:
// 存储子构件(可以是File或Folder)
vector<FileSystemComponent*> children_;
public:
Folder(const string& name) : FileSystemComponent(name) {}
// 析构函数:递归删除所有子构件,避免内存泄漏
~Folder() override {
cout << "删除文件夹:" << name_ << "(包含所有子元素)" << endl;
for (auto component : children_) {
delete component;
}
children_.clear();
}
// 实现show接口:显示文件夹名,递归显示所有子构件
void show(int depth = 0) const override {
string indent(depth * 2, ' ');
cout << indent << "+ 文件夹:" << name_ << endl;
// 递归显示所有子构件,层级+1
for (auto component : children_) {
component->show(depth + 1);
}
}
// 实现getSize接口:累加所有子构件的大小
int getSize() const override {
int totalSize = 0;
for (auto component : children_) {
totalSize += component->getSize();
}
return totalSize;
}
// 实现add接口:添加子构件到列表
void add(FileSystemComponent* component) override {
if (component) {
children_.push_back(component);
cout << "成功添加 " << component->name_ << " 到 " << name_ << endl;
}
}
// 实现remove接口:从列表中删除子构件(并释放内存)
void remove(FileSystemComponent* component) override {
if (!component) return;
for (auto it = children_.begin(); it != children_.end(); ++it) {
if (*it == component) {
cout << "成功删除 " << component->name_ << " 从 " << name_ << endl;
delete *it; // 释放子构件内存
children_.erase(it);
return;
}
}
cout << "错误:在 " << name_ << " 中未找到 " << component->name_ << endl;
}
};
// --------------- 客户端调用 ---------------
int main() {
// 1. 创建文件(叶子构件)
FileSystemComponent* file1 = new File("笔记.txt", 10);
FileSystemComponent* file2 = new File("图片.png", 200);
FileSystemComponent* file3 = new File("视频.mp4", 10240);
FileSystemComponent* file4 = new File("代码.cpp", 50);
// 2. 创建文件夹(容器构件)
FileSystemComponent* docFolder = new Folder("文档");
FileSystemComponent* mediaFolder = new Folder("媒体");
FileSystemComponent* rootFolder = new Folder("根目录");
// 3. 组合树形结构:根目录包含文档和媒体文件夹
rootFolder->add(docFolder);
rootFolder->add(mediaFolder);
// 文档文件夹包含笔记和代码文件
docFolder->add(file1);
docFolder->add(file4);
// 媒体文件夹包含图片和视频文件
mediaFolder->add(file2);
mediaFolder->add(file3);
// 4. 统一调用show接口:显示整个文件系统结构
cout << "===== 文件系统结构 =====" << endl;
rootFolder->show(); // 无需区分文件和文件夹,直接调用统一接口
// 5. 统一调用getSize接口:计算总大小
cout << "\n===== 大小统计 =====" << endl;
cout << "文档文件夹总大小:" << docFolder->getSize() << "KB" << endl;
cout << "媒体文件夹总大小:" << mediaFolder->getSize() << "KB" << endl;
cout << "根目录总大小:" << rootFolder->getSize() << "KB" << endl;
// 6. 测试删除操作
cout << "\n===== 测试删除操作 =====" << endl;
docFolder->remove(file1); // 从文档文件夹删除笔记.txt
mediaFolder->add(file1); // 错误:笔记.txt已被删除,内存已释放(实际开发中需避免野指针)
file1 = new File("笔记_v2.txt", 15); // 重新创建文件
mediaFolder->add(file1); // 成功添加到媒体文件夹
// 重新显示结构和大小
cout << "\n===== 删除后的文件系统结构 =====" << endl;
rootFolder->show();
cout << "根目录总大小:" << rootFolder->getSize() << "KB" << endl;
// 7. 释放资源(根目录析构函数会递归删除所有子构件)
delete rootFolder;
return 0;
}
3. 代码解析与运行效果
运行效果(关键输出):
===== 文件系统结构 =====
+ 文件夹:根目录
+ 文件夹:文档
- 文件:笔记.txt(大小:10KB)
- 文件:代码.cpp(大小:50KB)
+ 文件夹:媒体
- 文件:图片.png(大小:200KB)
- 文件:视频.mp4(大小:10240KB)
===== 大小统计 =====
文档文件夹总大小:60KB
媒体文件夹总大小:10440KB
根目录总大小:10500KB
===== 测试删除操作 =====
成功删除 笔记.txt 从 文档
错误:媒体 不支持添加操作(不是文件夹)// 因为file1已被释放,野指针导致错误
成功添加 笔记_v2.txt 到 媒体
===== 删除后的文件系统结构 =====
+ 文件夹:根目录
+ 文件夹:文档
- 文件:代码.cpp(大小:50KB)
+ 文件夹:媒体
- 文件:图片.png(大小:200KB)
- 文件:视频.mp4(大小:10240KB)
- 文件:笔记_v2.txt(大小:15KB)
根目录总大小:10505KB
删除文件夹:根目录(包含所有子元素)
删除文件夹:文档(包含所有子元素)
删除文件夹:媒体(包含所有子元素)
核心亮点:
- 统一接口:客户端调用
show()和getSize()时,无需判断是文件还是文件夹,完全透明; - 递归处理:文件夹的
show()和getSize()会自动递归处理子构件,无需客户端手动遍历; - 扩展灵活:新增 “压缩包”(既是文件又是容器)时,只需新增
ZipFile类实现FileSystemComponent接口,无需修改原有代码; - 内存安全:文件夹析构函数递归删除所有子构件,避免内存泄漏。
关键代码解释:
- 抽象构件的默认接口:
add()和remove()提供默认实现,文件类无需重写,文件夹类重写实现; - 层级缩进:
show()方法的depth参数控制缩进,让结构更清晰; - 容器的子构件管理:
vector<FileSystemComponent*>存储子构件,支持混合存储文件和文件夹。
四、C++ 实战示例 2:进阶版组织架构(递归统计功能)

基础示例讲解了组合模式的核心结构,接下来我们实现一个更贴近实际开发的组织架构场景,新增 “递归统计薪资” 和 “层级管理” 功能,体现组合模式在复杂树形结构中的价值。
1. 场景需求
- 组织架构包含 “员工”(叶子)和 “部门”(容器),部门可以嵌套子部门;
- 支持功能:显示组织架构、统计部门总薪资、添加 / 删除员工 / 部门、查询员工所在部门;
- 员工属性:姓名、职位、薪资;
- 部门属性:部门名称、部门负责人。
2. 完整 C++ 代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// --------------- 抽象构件(OrganizationComponent)---------------
// 组织架构抽象构件:统一员工和部门的操作接口
class OrganizationComponent {
protected:
string name_; // 员工姓名/部门名称
public:
OrganizationComponent(const string& name) : name_(name) {}
virtual ~OrganizationComponent() {}
// 统一接口1:显示组织架构(带层级)
virtual void show(int depth = 0) const = 0;
// 统一接口2:统计总薪资
virtual double calculateSalary() const = 0;
// 统一接口3:添加成员(员工不支持,部门支持)
virtual void add(OrganizationComponent* component) {
cout << "错误:" << name_ << " 不支持添加成员(不是部门)" << endl;
}
// 统一接口4:删除成员(员工不支持,部门支持)
virtual void remove(OrganizationComponent* component) {
cout << "错误:" << name_ << " 不支持删除成员(不是部门)" << endl;
}
// 统一接口5:查询成员是否存在
virtual bool find(const string& name) const = 0;
};
// --------------- 叶子构件(Employee)---------------
// 员工类:叶子构件,无下属
class Employee : public OrganizationComponent {
private:
string position_; // 职位
double salary_; // 月薪(单位:元)
public:
Employee(const string& name, const string& position, double salary)
: OrganizationComponent(name), position_(position), salary_(salary) {}
// 显示员工信息(带层级缩进)
void show(int depth = 0) const override {
string indent(depth * 2, ' ');
cout << indent << "- 员工:" << name_ << " | 职位:" << position_ << " | 月薪:" << salary_ << "元" << endl;
}
// 统计薪资:返回员工自身薪资
double calculateSalary() const override {
return salary_;
}
// 查询成员:判断是否是当前员工
bool find(const string& name) const override {
return name == name_;
}
// 获取职位(供外部查询)
string getPosition() const {
return position_;
}
};
// --------------- 容器构件(Department)---------------
// 部门类:容器构件,包含员工和子部门
class Department : public OrganizationComponent {
private:
vector<OrganizationComponent*> members_; // 部门成员(员工或子部门)
string manager_; // 部门负责人(员工姓名)
public:
Department(const string& name, const string& manager)
: OrganizationComponent(name), manager_(manager) {}
// 析构函数:递归删除所有成员
~Department() override {
cout << "解散部门:" << name_ << "(包含所有成员)" << endl;
for (auto member : members_) {
delete member;
}
members_.clear();
}
// 显示部门信息,递归显示所有成员
void show(int depth = 0) const override {
string indent(depth * 2, ' ');
cout << indent << "+ 部门:" << name_ << " | 负责人:" << manager_ << endl;
// 递归显示成员,层级+1
for (auto member : members_) {
member->show(depth + 1);
}
}
// 统计总薪资:累加所有成员的薪资
double calculateSalary() const override {
double totalSalary = 0.0;
for (auto member : members_) {
totalSalary += member->calculateSalary();
}
return totalSalary;
}
// 添加成员(员工或子部门)
void add(OrganizationComponent* component) override {
if (component) {
members_.push_back(component);
cout << "成功添加 " << component->name_ << " 到 " << name_ << endl;
}
}
// 删除成员
void remove(OrganizationComponent* component) override {
if (!component) return;
auto it = find(members_.begin(), members_.end(), component);
if (it != members_.end()) {
cout << "成功删除 " << component->name_ << " 从 " << name_ << endl;
delete *it;
members_.erase(it);
} else {
cout << "错误:在 " << name_ << " 中未找到 " << component->name_ << endl;
}
}
// 查询成员:递归查询所有子成员
bool find(const string& name) const override {
// 先判断部门负责人是否是目标(可选逻辑)
if (name == manager_) {
return true;
}
// 递归查询所有成员
for (auto member : members_) {
if (member->find(name)) {
return true;
}
}
return false;
}
// 获取部门负责人
string getManager() const {
return manager_;
}
};
// --------------- 客户端调用 ---------------
int main() {
// 1. 创建员工(叶子构件)
OrganizationComponent* emp1 = new Employee("张三", "后端开发工程师", 15000);
OrganizationComponent* emp2 = new Employee("李四", "前端开发工程师", 13000);
OrganizationComponent* emp3 = new Employee("王五", "测试工程师", 10000);
OrganizationComponent* emp4 = new Employee("赵六", "产品经理", 18000);
OrganizationComponent* emp5 = new Employee("孙七", "UI设计师", 12000);
OrganizationComponent* emp6 = new Employee("周八", "技术总监", 30000);
// 2. 创建部门(容器构件)
OrganizationComponent* devDept = new Department("研发部", "周八");
OrganizationComponent* productDept = new Department("产品部", "赵六");
OrganizationComponent* company = new Department("科技有限公司", "周八");
// 3. 组合组织架构
company->add(devDept);
company->add(productDept);
// 研发部包含后端、前端、测试员工
devDept->add(emp1);
devDept->add(emp2);
devDept->add(emp3);
devDept->add(emp6); // 技术总监属于研发部
// 产品部包含产品经理和UI设计师
productDept->add(emp4);
productDept->add(emp5);
// 4. 显示组织架构
cout << "===== 公司组织架构 =====" << endl;
company->show();
// 5. 统计薪资
cout << "\n===== 薪资统计 =====" << endl;
cout << "研发部月总薪资:" << devDept->calculateSalary() << "元" << endl;
cout << "产品部月总薪资:" << productDept->calculateSalary() << "元" << endl;
cout << "公司月总薪资:" << company->calculateSalary() << "元" << endl;
// 6. 测试查询功能
cout << "\n===== 成员查询 =====" << endl;
string queryName1 = "张三";
string queryName2 = "吴九";
cout << "是否存在员工 " << queryName1 << ":" << (company->find(queryName1) ? "是" : "否") << endl;
cout << "是否存在员工 " << queryName2 << ":" << (company->find(queryName2) ? "是" : "否") << endl;
// 7. 测试添加/删除功能
cout << "\n===== 组织调整 =====" << endl;
OrganizationComponent* emp7 = new Employee("吴九", "运维工程师", 11000);
devDept->add(emp7); // 给研发部添加运维工程师
productDept->remove(emp5); // 从产品部删除UI设计师
OrganizationComponent* newDept = new Department("设计部", "孙七");
newDept->add(emp5); // 重新创建设计部,添加UI设计师
company->add(newDept); // 公司添加设计部
// 重新显示架构和薪资
cout << "\n===== 调整后的组织架构 =====" << endl;
company->show();
cout << "公司月总薪资:" << company->calculateSalary() << "元" << endl;
// 8. 释放资源
delete company;
return 0;
}
3. 代码解析与实际价值
核心扩展点:
- 递归查询功能:
find()方法递归遍历所有子构件,支持从根节点查询整个树形结构中的成员; - 业务属性扩展:员工增加职位、薪资属性,部门增加负责人属性,更贴近实际业务;
- 复杂层级管理:支持多层嵌套部门(如公司→研发部→子部门),只需新增部门对象并添加到父部门即可。
实际应用价值:
- 组织架构管理系统:支持部门和员工的统一管理,统计薪资、查询成员、调整组织架构都无需区分对象类型;
- 可扩展性强:新增 “子公司”(容器)或 “实习生”(叶子)时,只需新增类实现
OrganizationComponent接口,原有代码无需修改; - 简化客户端逻辑:客户端无需关心组织架构的层级深度,统一调用接口即可完成操作。
五、C++ 实战示例 3:项目级菜单系统(组合模式 + 迭代器)

在实际项目中,组合模式常与迭代器模式结合,用于遍历树形结构。下面我们实现一个项目级的菜单系统,支持多级菜单、菜单点击事件、快捷键设置,以及菜单遍历功能。
1. 场景需求
- 菜单系统包含 “菜单项”(叶子,如 “文件→新建”)和 “菜单组”(容器,如 “文件”“编辑”);
- 支持功能:显示菜单(带快捷键)、点击菜单触发事件、遍历所有菜单、添加 / 删除菜单;
- 菜单属性:名称、快捷键、点击事件(回调函数)。
2. 完整 C++ 代码
#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <iterator>
using namespace std;
// --------------- 抽象构件(MenuComponent)---------------
// 菜单抽象构件:统一菜单项和菜单组的接口
class MenuComponent {
protected:
string name_; // 菜单名称
string shortcut_; // 快捷键(如Ctrl+N)
// 点击事件回调函数(无返回值,无参数)
function<void()> clickCallback_;
public:
MenuComponent(const string& name, const string& shortcut = "")
: name_(name), shortcut_(shortcut) {}
virtual ~MenuComponent() {}
// 统一接口1:显示菜单(带层级和快捷键)
virtual void show(int depth = 0) const = 0;
// 统一接口2:点击菜单(触发事件)
virtual void click() const = 0;
// 统一接口3:添加子菜单(菜单项不支持,菜单组支持)
virtual void add(MenuComponent* component) {
cout << "错误:" << name_ << " 不支持添加子菜单(不是菜单组)" << endl;
}
// 统一接口4:删除子菜单
virtual void remove(MenuComponent* component) {
cout << "错误:" << name_ << " 不支持删除子菜单(不是菜单组)" << endl;
}
// 统一接口5:遍历所有菜单(迭代器模式结合)
virtual void traverse(function<void(MenuComponent*)> handler) {
handler(this); // 处理当前菜单
}
// 设置点击事件回调
void setClickCallback(function<void()> callback) {
clickCallback_ = callback;
}
// 获取菜单名称和快捷键
string getName() const { return name_; }
string getShortcut() const { return shortcut_; }
};
// --------------- 叶子构件(MenuItem)---------------
// 菜单项:叶子构件,无子女菜单
class MenuItem : public MenuComponent {
public:
MenuItem(const string& name, const string& shortcut = "")
: MenuComponent(name, shortcut) {}
// 显示菜单项(带层级和快捷键)
void show(int depth = 0) const override {
string indent(depth * 2, ' ');
string shortcut = shortcut_.empty() ? "" : "(" + shortcut_ + ")";
cout << indent << "- " << name_ << shortcut << endl;
}
// 点击菜单项:触发回调函数
void click() const override {
cout << "点击菜单项:" << name_;
if (!shortcut_.empty()) {
cout << "(快捷键:" << shortcut_ << ")";
}
cout << endl;
if (clickCallback_) {
clickCallback_(); // 执行回调函数
}
}
// 遍历:菜单项无子女,只需处理自身(使用父类默认实现)
};
// --------------- 容器构件(MenuGroup)---------------
// 菜单组:容器构件,包含菜单项和子菜单组
class MenuGroup : public MenuComponent {
private:
vector<MenuComponent*> children_; // 子菜单列表
public:
MenuGroup(const string& name) : MenuComponent(name) {}
// 析构函数:递归删除所有子菜单
~MenuGroup() override {
cout << "销毁菜单组:" << name_ << endl;
for (auto child : children_) {
delete child;
}
children_.clear();
}
// 显示菜单组:递归显示所有子菜单
void show(int depth = 0) const override {
string indent(depth * 2, ' ');
cout << indent << "+ " << name_ << endl;
for (auto child : children_) {
child->show(depth + 1);
}
}
// 点击菜单组:菜单组本身不可点击,提示用户选择子菜单
void click() const override {
cout << "菜单组 " << name_ << " 不可直接点击,请选择子菜单项" << endl;
}
// 添加子菜单
void add(MenuComponent* component) override {
if (component) {
children_.push_back(component);
cout << "成功添加 " << component->getName() << " 到 " << name_ << endl;
}
}
// 删除子菜单
void remove(MenuComponent* component) override {
if (!component) return;
auto it = find(children_.begin(), children_.end(), component);
if (it != children_.end()) {
cout << "成功删除 " << component->getName() << " 从 " << name_ << endl;
delete *it;
children_.erase(it);
} else {
cout << "错误:在 " << name_ << " 中未找到 " << component->getName() << endl;
}
}
// 遍历:递归处理自身和所有子菜单
void traverse(function<void(MenuComponent*)> handler) override {
handler(this); // 处理当前菜单组
for (auto child : children_) {
child->traverse(handler); // 递归遍历子菜单
}
}
};
// --------------- 客户端调用 ---------------
int main() {
// 1. 创建菜单项(叶子构件)
MenuComponent* newItem = new MenuItem("新建", "Ctrl+N");
MenuComponent* openItem = new MenuItem("打开", "Ctrl+O");
MenuComponent* saveItem = new MenuItem("保存", "Ctrl+S");
MenuComponent* exitItem = new MenuItem("退出", "Alt+F4");
MenuComponent* copyItem = new MenuItem("复制", "Ctrl+C");
MenuComponent* pasteItem = new MenuItem("粘贴", "Ctrl+V");
MenuComponent* cutItem = new MenuItem("剪切", "Ctrl+X");
MenuComponent* aboutItem = new MenuItem("关于", "F1");
// 2. 设置菜单项点击事件回调
newItem->setClickCallback([]() {
cout << "执行新建操作:创建空白文档" << endl;
});
saveItem->setClickCallback([]() {
cout << "执行保存操作:文档已保存到本地" << endl;
});
exitItem->setClickCallback([]() {
cout << "执行退出操作:程序即将关闭" << endl;
});
aboutItem->setClickCallback([]() {
cout << "执行关于操作:版本 v1.0.0 | 版权所有 © 2025" << endl;
});
// 3. 创建菜单组(容器构件)
MenuComponent* fileGroup = new MenuGroup("文件");
MenuComponent* editGroup = new MenuGroup("编辑");
MenuComponent* helpGroup = new MenuGroup("帮助");
MenuComponent* mainMenu = new MenuGroup("主菜单");
// 4. 组合菜单结构
mainMenu->add(fileGroup);
mainMenu->add(editGroup);
mainMenu->add(helpGroup);
fileGroup->add(newItem);
fileGroup->add(openItem);
fileGroup->add(saveItem);
fileGroup->add(exitItem);
editGroup->add(copyItem);
editGroup->add(cutItem);
editGroup->add(pasteItem);
helpGroup->add(aboutItem);
// 5. 显示菜单系统
cout << "===== 程序菜单系统 =====" << endl;
mainMenu->show();
// 6. 测试点击功能
cout << "\n===== 测试点击功能 =====" << endl;
newItem->click(); // 点击新建
saveItem->click(); // 点击保存
fileGroup->click(); // 点击菜单组(不可直接点击)
aboutItem->click(); // 点击关于
// 7. 测试遍历功能:输出所有菜单的名称和快捷键
cout << "\n===== 遍历所有菜单 =====" << endl;
mainMenu->traverse([](MenuComponent* component) {
string shortcut = component->getShortcut().empty() ? "无" : component->getShortcut();
cout << "菜单名称:" << component->getName() << " | 快捷键:" << shortcut << endl;
});
// 8. 测试添加/删除功能
cout << "\n===== 菜单调整 =====" << endl;
MenuComponent* exportItem = new MenuItem("导出", "Ctrl+E");
exportItem->setClickCallback([]() {
cout << "执行导出操作:文档已导出为PDF" << endl;
});
fileGroup->add(exportItem); // 给文件菜单添加导出项
editGroup->remove(cutItem); // 删除编辑菜单的剪切项
// 重新显示菜单
cout << "\n===== 调整后的菜单系统 =====" << endl;
mainMenu->show();
// 9. 释放资源
delete mainMenu;
return 0;
}
3. 代码解析与项目价值
核心技术亮点:
- 回调函数结合:使用
std::function实现菜单点击事件的灵活绑定,支持不同菜单项的自定义逻辑; - 迭代器模式融合:
traverse()方法支持遍历整个菜单树,客户端可通过 lambda 表达式自定义处理逻辑(如批量设置快捷键、搜索菜单); - 层级化显示:通过
depth参数控制菜单缩进,符合桌面应用的菜单显示习惯。
项目级应用价值:
- 桌面应用菜单系统:直接复用该代码,可快速实现复杂的多级菜单,支持快捷键和自定义点击事件;
- 网页导航菜单:稍作修改(如将
show()改为生成 HTML),可用于前端导航菜单的后端逻辑处理; - 可扩展性强:新增 “视图” 菜单组或 “撤销” 菜单项时,只需新增对象并添加到对应菜单组,无需修改原有代码。
六、组合模式的应用场景与优缺点

1. 典型应用场景
组合模式的核心是 “统一单个对象和组合对象的操作”,以下场景优先考虑使用:
- 存在 “部分 - 整体” 的树形结构关系(如文件系统、组织架构、菜单系统、目录结构);
- 客户端需要统一处理单个对象和组合对象,无需区分它们的类型;
- 需要递归遍历树形结构,执行相同的操作(如统计、显示、搜索、删除);
- 希望简化树形结构的扩展和维护,新增 “部分” 或 “整体” 时无需修改客户端代码。
2. 优点
- 统一接口,简化客户端逻辑:客户端无需区分单个对象和组合对象,直接调用统一接口,代码更简洁;
- 简化树形结构的处理:递归遍历由容器构件实现,客户端无需手动编写遍历逻辑;
- 符合 “开闭原则”:新增叶子或容器构件时,只需实现抽象接口,无需修改原有代码;
- 灵活性高:支持任意层级的嵌套组合,树形结构可动态调整(添加 / 删除子构件)。
3. 缺点
- 设计复杂度提升:需要定义抽象构件、叶子构件、容器构件,对新手不友好;
- 某些操作不适用:如果叶子和容器的操作差异过大(如容器有 “添加”,叶子无),抽象接口会包含叶子不支持的方法,违反 “单一职责原则”;
- 性能开销:递归遍历树形结构时,层级过深可能导致栈溢出(可通过迭代遍历优化)。
4. 优化建议
- 对于叶子不支持的操作(如
add()),可采用 “空实现” 或 “抛出异常”,避免客户端误调用; - 层级过深的树形结构,将递归遍历改为迭代遍历(如使用栈或队列),避免栈溢出;
- 结合工厂模式创建构件对象,隐藏对象创建细节,简化客户端代码;
- 结合迭代器模式遍历子构件,让容器构件的遍历逻辑更灵活(如示例 3 所示)。
七、组合模式与其他设计模式的区别

很多开发者会混淆组合模式与装饰器模式、桥接模式,这里用通俗的语言讲清它们的核心区别:
1. 组合模式 vs 装饰器模式
- 核心目的不同:
- 组合模式:关注 “部分 - 整体” 的树形结构,统一单个对象和组合对象的操作;
- 装饰器模式:关注 “功能增强”,在不改变原类结构的前提下,动态给对象添加功能。
- 结构关系不同:
- 组合模式:容器构件 “包含” 子构件(聚合关系),子构件可以是叶子或其他容器;
- 装饰器模式:装饰器 “包装” 被装饰者(关联关系),装饰器和被装饰者实现同一接口。
- 示例对比:
- 组合模式:文件夹包含文件和子文件夹,统一显示和计算大小;
- 装饰器模式:给文件添加 “加密”“压缩” 功能,增强文件的能力。
2. 组合模式 vs 桥接模式
- 核心目的不同:
- 组合模式:解决 “树形结构中单个对象和组合对象的统一操作” 问题;
- 桥接模式:解决 “多维度独立变化” 问题,解耦抽象和实现。
- 适用场景不同:
- 组合模式:适用于树形结构,强调 “部分 - 整体” 的层级关系;
- 桥接模式:适用于多维度变化的场景(如形状 + 颜色),强调维度的独立扩展。
- 结构不同:
- 组合模式:只有一个抽象构件层级,容器构件包含子构件;
- 桥接模式:有两个或多个抽象层级(如抽象化 + 实现化),通过组合关联。
3. 组合模式 vs 迭代器模式
- 核心目的不同:
- 组合模式:统一单个对象和组合对象的操作,管理树形结构的子构件;
- 迭代器模式:提供一种统一的遍历集合的方式,隐藏集合的内部结构。
- 关系:两者常结合使用 —— 组合模式管理树形结构,迭代器模式提供遍历该结构的方法(如示例 3 的
traverse()方法)。
八、总结:组合模式的核心价值

组合模式的本质是 “树形结构的统一化处理”—— 通过定义统一接口,让客户端 “忽略单个对象和组合对象的差异”,专注于业务逻辑而非结构判断。它不是一个 “万能模式”,但在树形结构场景中,能极大简化代码,提升扩展性和维护性。
学习组合模式的关键,不是死记硬背角色定义,而是学会 “识别树形结构场景” 和 “设计统一接口”。实际开发中,很多场景都隐含树形结构(如权限系统的角色层级、电商系统的商品分类),只要需要统一处理 “部分” 和 “整体”,组合模式就是最优解之一。
最后给大家一个落地建议:
- 先判断场景是否为树形结构,是否需要统一操作单个对象和组合对象;
- 设计抽象接口时,只包含核心操作,避免添加过多叶子不支持的方法;
- 容器构件的递归逻辑要注意边界条件(如空容器、深层嵌套),避免栈溢出;
- 可结合工厂模式、迭代器模式优化代码,提升可维护性。

被折叠的 条评论
为什么被折叠?



