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

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


组合模式的核心思想

什么是组合模式?

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

组合模式的结构

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

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

案例:公司组织架构

案例背景

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

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

代码分块及讲解

1. 抽象组件类

这是组合模式的核心部分,所有叶子节点和组合节点都必须实现这个抽象类。通过这个类,我们可以定义统一的操作接口。

// 抽象组件类: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. 叶子节点类

叶子节点是树形结构的最底层元素,它不包含任何子节点。比如在组织架构中,员工就是叶子节点。

// 叶子节点类: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. 组合节点类

组合节点可以包含其他节点(叶子节点或组合节点),并负责管理它们。比如一个部门可以包含其他部门或员工。

// 组合节点类: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. 客户端代码

在客户端代码中,我们将创建叶子节点(员工)和组合节点(部门),并将它们组织成树形结构,最后输出公司组织架构。

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组件树等场景。

源码分享

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/64652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ElasticSearch 自动补全

1、前言 当用户在搜索框输入字符时&#xff0c;我们应该提示出与该字符有关的搜索项&#xff0c;根据用户输入的字母&#xff0c;提示完整词条的功能&#xff0c;就是自动补全。 2、安装拼音分词器 Github地址&#xff1a;https://github.com/infinilabs/analysis-pinyin 插件…

UML 建模实验

文章目录 实验一 用例图一、安装并熟悉软件EnterpriseArchitect16二、用例图建模 实验二 类图、包图、对象图类图第一题第二题 包图对象图第一题第二题 实验三 顺序图、通信图顺序图银行系统学生指纹考勤系统饮料自动销售系统“买到饮料”“饮料已售完”“无法找零”完整版 通信…

Linux环境下 搭建ELk项目 -单机版练习

前言 ELK 项目是一个由三个开源工具组成的日志处理和分析解决方案&#xff0c;ELK 是 Elasticsearch、Logstash 和 Kibana 的首字母缩写。这个项目的目标是帮助用户采集、存储、搜索和可视化大量的日志和事件数据&#xff0c;尤其是在分布式系统中。下面是每个组件的概述&…

探索 Vue.js 组件开发:从基础到进阶的完整指南

引言 在现代前端开发中&#xff0c;Vue.js 凭借其易用性和强大的功能&#xff0c;成为了开发者钟爱的框架之一。其核心理念——组件化开发&#xff0c;不仅让代码更加模块化、可维护&#xff0c;还大大提高了开发效率。本文将从基础入手&#xff0c;详细探讨 Vue.js 组件开发的…

智能工厂的设计软件 三种处理单元(NPU/GPU/CPU)及其在深度学习框架中的作用 之3(百度文库答问 之1)

Q&A&#xff08;百度文库&#xff09; Q1、今天聊聊“智能工厂的设计软件”中的三种处理单元&#xff08;NPU/GPU/CPU&#xff09;。一般来说提起这三者就不得不说“深度学习”。那我们就从这里开始。 请先给出一个程序例子来说明NPU 如何协作CPU和GPU来完成深度学习任务 …

jdk 离线安装脚本

jdk 离线安装脚本 说明脚本使用完整脚本脚本内容说明1、是否卸载原有jdk&#xff0c;检查安装包是否正确2、先卸载、再安装并检验安装成果 说明 经常装服务器环境&#xff0c;根据以前的安装经验写了个安装脚本。本人不是专业运维&#xff0c;也是边百度边写的&#xff0c;发现…

HTTP 常见的请求头有哪些? 作用?常见的使用场景都有哪些?

在 HTTP 协议中,**请求头(Request Headers)**是客户端向服务器发送请求时附带的元数据,主要用于传递请求的相关信息,比如客户端信息、请求的格式要求、认证信息等。理解这些请求头的作用和使用场景对于开发现代 Web 应用至关重要。以下是一些常见的 HTTP 请求头及其作用和…

day14-16系统服务管理和ntp和防火墙

一、自有服务概述 服务是一些特定的进程&#xff0c;自有服务就是系统开机后就自动运行的一些进程&#xff0c;一旦客户发出请求&#xff0c;这些进程就自动为他们提供服务&#xff0c;windows系统中&#xff0c;把这些自动运行的进程&#xff0c;称为"服务" window…

2024年底-Sre面试回顾

前言 背景: 2024.11月底 公司不大行了, 裁员收缩, 12月初开始面试, 2周大概面试了十几家公司, 3个2面要去线下, 有1个还不错的offer, 想结束战斗但还没到时候 个人情况: base上海 5年经验(2年实施3年运维半年开发) 面试岗位: Sre、云原生运维、驻场运维、高级运维、实施交付 …

pytest入门十:配置文件

pytest.ini&#xff1a;pytest的主配置文件&#xff0c;可以改变pytest的默认行为conftest.py&#xff1a;测试用例的一些fixture配置 pytest.ini marks mark 打标的执行 pytest.mark.add add需要些marks配置否则报warning [pytest] markersadd:测试打标 测试用例中添加了 p…

【Rust自学】3.6. 控制流:循环

3.6.0. 写在正文之前 欢迎来到Rust自学的第三章&#xff0c;一共有6个小节&#xff0c;分别是: 变量与可变性数据类型&#xff1a;标量类型数据类型&#xff1a;复合类型函数和注释控制流&#xff1a;if else控制流&#xff1a;循环&#xff08;本文&#xff09; 通过第二章…

【C#】方法参数的修饰符ref 与 out

在 C# 中&#xff0c;ref 和 out 是方法参数的修饰符&#xff0c;用于将参数 按引用传递&#xff0c;而不是按值传递。这允许方法修改调用者传递的变量的值。尽管它们的行为类似&#xff0c;但有重要的区别和适用场景。 1. ref 的含义与使用 含义 引用传递&#xff1a; 参数通…

js进阶语法详解

文章目录 js进阶语法详解一、引言二、闭包与作用域1、闭包1.1、示例代码 2、作用域2.1、示例代码 三、this关键字与函数调用1、this的指向1.1、示例代码 2、apply和call方法2.1、示例代码 四、异步编程1、Promise1.1、示例代码 五、JS的面向对象封装1、封装的概念1.1、构造函数…

Qt WORD/PDF(一)使用 QtPdfium库实现 PDF 预览

文章目录 一、简介二、下载 QtPdfium三、加载 QtPdfium 动态库四、Demo 使用 关于QT Widget 其它文章请点击这里: QT Widget 国际站点 GitHub: https://github.com/chenchuhan 国内站点 Gitee : https://gitee.com/chuck_chee 姊妹篇: Qt WORD/PDF&#x…

.Net WebAPI(一)

文章目录 项目地址一、WebAPI基础1. 项目初始化1.1 创建简单的API1.1.1 get请求1.1.2 post请求1.1.3 put请求1.1.4 Delete请求 1.2 webapi的流程 2.Controllers2.1 创建一个shirts的Controller 3. Routing3.1 使用和创建MapControllers3.2 使用Routing的模板语言 4. Mould Bind…

备战美赛!2025美赛数学建模C题模拟预测!用于大家练手模拟!

完整的思路代码模型见文末 2025 美赛数学建模 C 题 模拟题&#xff1a;城市交通拥堵指数的预测与管理策略 背景 随着全球城市化进程的加快&#xff0c;交通拥堵问题成为城市发展的重要挑战之一。交通拥堵不仅影响居民出行效率&#xff0c;还增加了能源消耗和碳排放。近年来&…

Java操作Redis-Jedis

介绍 前面我们讲解了Redis的常用命令&#xff0c;这些命令是我们操作Redis的基础&#xff0c;那么我们在 java程序中应该如何操作Redis呢&#xff1f;这就需要使用Redis的Java客户端&#xff0c;就如同我们使 用JDBC操作MySQL数据库一样。 Redis 的 Java 客户端很多&#xff0…

英语-日常笔记-2

You can refer to the previous code logic.你可以参考以前的代码逻辑 只是刚开始做 just started doing it/just began to do it 由你自己决定 it’s up to you /it depends on you 这一版需求不急 this demand version is not urgent there is no rush for this requireme…

Vue3 + Element-Plus + vue-draggable-plus 实现图片拖拽排序和图片上传到阿里云 OSS 父组件实现真正上传(最新保姆级)

Vue3 Element-Plus vue-draggable-plus 实现图片拖拽排序和图片上传到阿里云 OSS&#xff08;最新保姆级&#xff09;父组件实现真正上传 1、效果展示2、UploadImage.vue 组件封装3、相关请求封装4、SwiperConfig.vue 调用组件5、后端接口 1、效果展示 如果没有安装插件&…

将 Ubuntu 22.04 LTS 升级到 24.04 LTS

Ubuntu 24.04 LTS 将支持 Ubuntu 桌面、Ubuntu 服务器和 Ubuntu Core 5 年&#xff0c;直到 2029 年 4 月。 本文将介绍如何将当前 Ubuntu 22.04 系统升级到最新 Ubuntu 24.04 LTS版本。 备份个人数据 以防万一&#xff0c;把系统中的重要数据自己备份一下~ 安装配置SSH访问…