组合模式实战:用 C++ 驯服树形结构,统一操作整体与部分

📕目录

前言

一、先搞懂:为什么需要组合模式?(从实际痛点出发)

场景 1:文件系统的 “双重逻辑” 困境

场景 2:组织架构的 “层级统计” 难题

核心痛点总结

二、组合模式核心原理:3 个角色 + 1 个核心思想

1. 核心思想

2. 3 个核心角色

(1)抽象构件(Component)

(2)叶子构件(Leaf)

(3)容器构件(Composite)

3. 结构示意图(文件系统场景)

三、C++ 实战示例 1:基础版文件系统(入门必看)

1. 实现步骤拆解

2. 完整 C++ 代码(可直接运行)

3. 代码解析与运行效果

运行效果(关键输出):

核心亮点:

关键代码解释:

四、C++ 实战示例 2:进阶版组织架构(递归统计功能)

1. 场景需求

2. 完整 C++ 代码

3. 代码解析与实际价值

核心扩展点:

实际应用价值:

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

1. 场景需求

2. 完整 C++ 代码

3. 代码解析与项目价值

核心技术亮点:

项目级应用价值:

六、组合模式的应用场景与优缺点

1. 典型应用场景

2. 优点

3. 缺点

4. 优化建议

七、组合模式与其他设计模式的区别

1. 组合模式 vs 装饰器模式

2. 组合模式 vs 桥接模式

3. 组合模式 vs 迭代器模式

八、总结:组合模式的核心价值


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;
    }
}

这种代码的问题很明显:

  1. 冗余重复:每个操作都要写一遍 “判断对象类型” 的逻辑;
  2. 耦合度高:客户端需要知道 “文件” 和 “文件夹” 的区别,违反 “依赖倒置原则”;
  3. 扩展困难:新增对象类型(如压缩包)或操作(如搜索)时,需要修改所有相关逻辑,违反 “开闭原则”。

核心痛点总结

这两个场景的共性是 “存在树形结构,且需要对‘单个对象’和‘组合对象’执行相同操作”。传统设计的核心问题是 “接口不统一”,而组合模式的出现,正是为了解决这个问题 —— 通过定义统一接口,让客户端 “对单个对象和组合对象一视同仁”,无需关心内部结构。

二、组合模式核心原理: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)

通过这个结构,客户端可以:

  • 直接调用FilegetSize()获取单个文件大小;
  • 调用FoldergetSize(),自动累加所有子文件和子文件夹的大小;
  • 无需区分是File还是Folder,统一调用show()就能显示结构。

三、C++ 实战示例 1:基础版文件系统(入门必看)

我们先通过最基础的文件系统场景,实现组合模式的核心逻辑 —— 统一文件(叶子)和文件夹(容器)的操作接口,支持显示结构、计算大小、添加 / 删除子元素。

1. 实现步骤拆解

  1. 定义抽象构件(FileSystemComponent):声明统一接口(show()getSize()add()remove());
  2. 实现叶子构件(File):实现show()getSize()add()remove()抛出不支持提示;
  3. 实现容器构件(Folder):维护子构件列表,实现所有接口,其中show()getSize()递归处理子构件;
  4. 客户端调用:创建文件和文件夹对象,组合成树形结构,调用统一接口完成操作。

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()方法)。

八、总结:组合模式的核心价值

组合模式的本质是 “树形结构的统一化处理”—— 通过定义统一接口,让客户端 “忽略单个对象和组合对象的差异”,专注于业务逻辑而非结构判断。它不是一个 “万能模式”,但在树形结构场景中,能极大简化代码,提升扩展性和维护性。

学习组合模式的关键,不是死记硬背角色定义,而是学会 “识别树形结构场景” 和 “设计统一接口”。实际开发中,很多场景都隐含树形结构(如权限系统的角色层级、电商系统的商品分类),只要需要统一处理 “部分” 和 “整体”,组合模式就是最优解之一。

最后给大家一个落地建议:

  1. 先判断场景是否为树形结构,是否需要统一操作单个对象和组合对象;
  2. 设计抽象接口时,只包含核心操作,避免添加过多叶子不支持的方法;
  3. 容器构件的递归逻辑要注意边界条件(如空容器、深层嵌套),避免栈溢出;
  4. 可结合工厂模式、迭代器模式优化代码,提升可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值