C++设计模式:组合模式(公司架构案例)

组合模式是一种非常有用的设计模式,用于解决**"部分-整体"**问题。它允许我们用树形结构来表示对象的层次结构,并且让客户端可以统一地操作单个对象和组合对象。


组合模式的核心思想

什么是组合模式?

组合模式的目的是将对象组织成树形结构,表示"部分-整体"的层次结构,使客户端能够以一致的方式处理单个对象和组合对象。

组合模式的结构

组合模式一般由以下三个部分组成:

  1. 组件(Component):定义了组合对象和叶子对象的通用接口。
  2. 叶子节点(Leaf):表示树的基本单元,不能再包含其他子对象。
  3. 组合节点(Composite):表示有子节点的对象,它实现了组件接口并且可以操作其子节点。
组合模式的特点
  1. 递归结构:树形结构的每个节点既可以是叶子,也可以是子树。
  2. 统一接口:客户端通过相同的接口处理单个对象和组合对象。

案例:公司组织架构

案例背景

我们需要设计一个程序来模拟公司组织架构:

  1. 公司有不同的部门(如总部、人力资源部、技术部等)。
  2. 每个部门可以包含其他部门和员工。
  3. 我们希望能够清晰地输出公司组织的层次结构。

代码分块及讲解

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用于控制输出的层次缩进。
  • addremove:用于操作子节点,默认为空实现,叶子节点无需实现这两个方法。
  • 虚析构函数:确保使用多态时,子类可以正确释放资源。

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控制层次缩进。
  • 叶子节点没有子节点 :因此不需要实现addremove方法。

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存储部门的子节点(可以是员工或其他部门)。
  • addremove:用于动态添加或移除子节点。
  • 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;
}

分析:

  1. 创建叶子节点(员工)
    • 创建三个员工对象,分别为"张三"、"李四"和"王五"。
  2. 创建组合节点(部门)
    • 创建两个部门,分别为"人力资源部"和"技术部"。
  3. 构建树结构
    • 将"张三"加入人力资源部。
    • 将"李四"和"王五"加入技术部。
    • 将两个部门加入公司的根节点"总部"。
  4. 输出组织架构
    • 调用display方法,从根节点开始递归输出整棵树的结构。

运行结果

运行程序后,输出如下:

复制代码
部门: 总部
--部门: 人力资源部
----员工: 张三
--部门: 技术部
----员工: 李四
----员工: 王五

输出说明:

  • 根节点"总部"包含两个子节点:人力资源部和技术部。
  • 每个部门又包含各自的员工信息,清晰地展示了整个公司组织结构。

组合模式的优缺点

优点
  1. 统一性:客户端代码可以统一处理单个对象和组合对象,简化了逻辑。
  2. 扩展性:可以轻松增加新的叶子节点或组合节点。
  3. 灵活性:支持动态调整树形结构(如添加、删除节点)。
缺点
  1. 复杂性:对于简单结构来说,使用组合模式会增加不必要的复杂性。
  2. 通用性限制:某些特定操作可能只适合叶子节点,但为了统一接口,组合节点也必须实现这些方法。

总结

组合模式通过将单个对象和组合对象统一处理,解决了"部分-整体"问题,特别适合树形结构的数据处理场景。通过案例代码我们可以看到:

  1. 使用组合模式可以清晰地表示公司组织架构。
  2. 递归调用的display方法是组合模式的核心,能够清晰输出树形层次结构。
  3. 组合模式广泛应用于组织架构、文件系统、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多平台发布

相关推荐
唐叔在学习10 分钟前
就算没有服务器,我照样能够同步数据
后端·python·程序员
用户68545375977691 小时前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo1 小时前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM971 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack2 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo2 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊2 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说2 小时前
基于Spark的配置化离线反作弊系统
后端
Java编程爱好者3 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端