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多平台发布

相关推荐
SomeB1oody35 分钟前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody41 分钟前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
无 证明1 小时前
new 分配空间;引用
数据结构·c++
啦啦右一2 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien2 小时前
Spring Boot常用注解
java·spring boot·后端
Damon_X3 小时前
桥接模式(Bridge Pattern)
设计模式·桥接模式
盛派网络小助手4 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生4 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
zquwei5 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
别NULL5 小时前
机试题——疯长的草
数据结构·c++·算法