【C++】组合模式

目录

  • 一、模式核心概念与结构
  • 二、C++ 实现示例:文件系统
  • 三、组合模式的关键特性
  • 四、应用场景
  • 五、组合模式与其他设计模式的关系
  • 六、C++ 标准库中的组合模式应用
  • 七、优缺点分析
  • 八、实战案例:图形编辑器
  • 九、实现注意事项
    • 如果这篇文章对你有所帮助,渴望获得你的一个点赞!

组合模式(Composite Pattern)是一种【结构型】设计模式,它允许你将对象组合成树形结构以表示 “部分 - 整体” 的层次关系。这种模式使得客户端可以统一处理单个对象和对象组合,无需区分它们的具体类型。

一、模式核心概念与结构

组合模式包含三个核心角色:

  1. 组件(Component):定义组合中所有对象的通用接口,声明管理子组件的方法。
  2. 叶节点(Leaf):表示组合中的叶节点对象,没有子节点,实现组件接口。
  3. 组合节点(Composite):表示组合中的分支节点,包含子组件,实现组件接口并管理子组件。

二、C++ 实现示例:文件系统

以下是一个经典的组合模式示例,演示如何用组合模式表示文件系统:

#include <iostream>
#include <string>
#include <vector>
#include <memory>// 组件:文件系统元素
class FileSystemElement {
public:virtual ~FileSystemElement() = default;virtual void print(int depth = 0) const = 0;virtual size_t getSize() const = 0;virtual void add(std::shared_ptr<FileSystemElement> element) {}virtual void remove(std::shared_ptr<FileSystemElement> element) {}
};// 叶节点:文件
class File : public FileSystemElement {
private:std::string name;size_t size;public:File(const std::string& n, size_t s) : name(n), size(s) {}void print(int depth) const override {std::cout << std::string(depth * 2, ' ') << "- " << name << " (file, " << size << " bytes)" << std::endl;}size_t getSize() const override {return size;}
};// 组合节点:目录
class Directory : public FileSystemElement {
private:std::string name;std::vector<std::shared_ptr<FileSystemElement>> children;public:Directory(const std::string& n) : name(n) {}void print(int depth) const override {std::cout << std::string(depth * 2, ' ') << "+ " << name << " (directory, " << getSize() << " bytes)" << std::endl;for (const auto& child : children) {child->print(depth + 1);}}size_t getSize() const override {size_t total = 0;for (const auto& child : children) {total += child->getSize();}return total;}void add(std::shared_ptr<FileSystemElement> element) override {children.push_back(element);}void remove(std::shared_ptr<FileSystemElement> element) override {for (auto it = children.begin(); it != children.end(); ++it) {if (*it == element) {children.erase(it);break;}}}
};// 客户端代码
int main() {// 创建目录结构auto root = std::make_shared<Directory>("/");auto home = std::make_shared<Directory>("home");auto user = std::make_shared<Directory>("user");auto docs = std::make_shared<Directory>("documents");// 添加文件docs->add(std::make_shared<File>("report.txt", 1024));docs->add(std::make_shared<File>("presentation.pdf", 5120));user->add(docs);user->add(std::make_shared<File>("profile.jpg", 2048));home->add(user);root->add(home);root->add(std::make_shared<File>("readme.txt", 512));// 打印文件系统结构root->print();// 计算总大小std::cout << "\nTotal size: " << root->getSize() << " bytes" << std::endl;return 0;
}

三、组合模式的关键特性

  1. 统一接口
    • 组件接口定义了叶节点和组合节点的共同行为(如print()getSize())。
    • 客户端可以一致地处理单个对象和对象组合。
  2. 递归结构
    • 组合节点可以包含其他组合节点或叶节点,形成树形结构。
    • 操作可以递归地应用于整个树结构。
  3. 透明性 vs 安全性
    • 透明性:在组件接口中声明所有管理子节点的方法(如add()remove()),使叶节点和组合节点具有相同接口,但可能导致叶节点运行时错误。
    • 安全性:仅在组合节点中声明管理子节点的方法,叶节点不包含这些方法,但客户端需区分叶节点和组合节点。

四、应用场景

  1. 树形结构表示
    • 文件系统、XML/JSON 解析树。
    • 组织结构图、菜单系统。
  2. 统一处理对象
    • 图形编辑器中的形状组合(如 Group、Layer)。
    • 游戏中的场景图(Scene Graph)。
  3. 递归操作
    • 数学表达式计算(如加减乘除组合)。
    • 权限管理中的角色和权限组。

五、组合模式与其他设计模式的关系

  1. 迭代器模式
    • 组合模式常与迭代器模式结合,用于遍历树形结构。
    • 例如,使用迭代器遍历文件系统中的所有文件。
  2. 访问者模式
    • 组合模式可以配合访问者模式,将算法与对象结构分离。
    • 例如,通过访问者模式实现文件系统的大小统计、搜索等操作。
  3. 享元模式
    • 组合模式的叶节点可以是享元对象,共享内部状态以节省内存。
    • 例如,文件系统中的相同文件可以共享同一个对象实例。

六、C++ 标准库中的组合模式应用

  1. STL 容器
    • std::vectorstd::list等容器可以存储不同类型的元素,形成树形结构。
    • 例如,std::vector<std::shared_ptr<Component>>可以存储组合节点和叶节点。
  2. 智能指针
    • std::shared_ptrstd::unique_ptr可用于管理组合结构中的对象生命周期。
    • 例如,在文件系统示例中使用std::shared_ptr避免内存泄漏。
  3. 流类库
    • std::iostream层次结构中,std::iostream是抽象组件,std::ifstreamstd::ofstream是叶节点,std::stringstream可视为组合节点。

七、优缺点分析

优点:

  • 简化客户端代码:客户端无需区分处理叶节点和组合节点。
  • 灵活扩展:可以轻松添加新的叶节点或组合节点。
  • 树形结构清晰:明确表示 “部分 - 整体” 的层次关系。

缺点:

  • 限制类型安全:透明实现可能导致运行时错误(如对叶节点调用add())。
  • 设计复杂度:在某些情况下,过度使用组合模式可能使设计变得复杂。
  • 性能问题:递归操作可能导致性能开销,尤其是大型树结构。

八、实战案例:图形编辑器

以下是一个图形编辑器的组合模式实现:

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <cmath>// 组件:图形元素
class Shape {
public:virtual ~Shape() = default;virtual void draw() const = 0;virtual double area() const = 0;virtual void add(std::shared_ptr<Shape> shape) {}virtual void remove(std::shared_ptr<Shape> shape) {}
};// 叶节点:圆形
class Circle : public Shape {
private:double radius;std::string color;public:Circle(double r, const std::string& c) : radius(r), color(c) {}void draw() const override {std::cout << "Drawing Circle with radius " << radius << " and color " << color << std::endl;}double area() const override {return M_PI * radius * radius;}
};// 叶节点:矩形
class Rectangle : public Shape {
private:double width, height;std::string color;public:Rectangle(double w, double h, const std::string& c) : width(w), height(h), color(c) {}void draw() const override {std::cout << "Drawing Rectangle with width " << width << ", height " << height << " and color " << color << std::endl;}double area() const override {return width * height;}
};// 组合节点:图形组
class Group : public Shape {
private:std::string name;std::vector<std::shared_ptr<Shape>> shapes;public:Group(const std::string& n) : name(n) {}void draw() const override {std::cout << "Group " << name << " contains:" << std::endl;for (const auto& shape : shapes) {shape->draw();}}double area() const override {double total = 0;for (const auto& shape : shapes) {total += shape->area();}return total;}void add(std::shared_ptr<Shape> shape) override {shapes.push_back(shape);}void remove(std::shared_ptr<Shape> shape) override {for (auto it = shapes.begin(); it != shapes.end(); ++it) {if (*it == shape) {shapes.erase(it);break;}}}
};// 客户端代码
int main() {// 创建图形元素auto circle = std::make_shared<Circle>(5.0, "red");auto rectangle = std::make_shared<Rectangle>(4.0, 6.0, "blue");// 创建图形组auto group1 = std::make_shared<Group>("Group 1");group1->add(circle);group1->add(rectangle);// 创建另一个图形组并添加子组auto group2 = std::make_shared<Group>("Group 2");auto anotherCircle = std::make_shared<Circle>(3.0, "green");group2->add(anotherCircle);group2->add(group1);  // 添加子组// 绘制所有图形group2->draw();// 计算总面积std::cout << "\nTotal area: " << group2->area() << std::endl;return 0;
}

九、实现注意事项

  1. 内存管理
    • 使用智能指针(如std::shared_ptr)管理组合结构中的对象生命周期。
    • 避免循环引用导致内存泄漏(可使用std::weak_ptr)。
  2. 接口设计
    • 根据需要选择透明性(在基类中声明所有方法)或安全性(仅在组合类中声明特定方法)。
  3. 递归深度
    • 对于大型树结构,递归操作可能导致栈溢出,考虑使用迭代或尾递归优化。
  4. 线程安全
    • 在多线程环境中,修改组合结构(如add()remove())需考虑同步问题。

组合模式是 C++ 中处理树形结构的重要工具,通过统一接口和递归组合,使客户端可以一致地处理单个对象和对象组合,从而简化了代码设计并提高了系统的可扩展性。


如果这篇文章对你有所帮助,渴望获得你的一个点赞!

在这里插入图片描述

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

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

相关文章

C++包管理工具:conan2持续集成 (CI) 教程

1.持续集成 (CI) ​ 这是一个高级主题&#xff0c;需要具备 Conan 的基础知识。请先阅读并练习用户教程。本节面向设计和实施涉及 Conan 包的生产 CI 管道的 DevOps 和构建工程师。如果不是这种情况&#xff0c;您可以跳过本节。 持续集成 (CI) 对不同用户和组织有不同的含义…

免费SSL证书一键申请与自动续期

免费SSL证书申请与自动续期教程 本文介绍如何通过乐此加密&#xff08;www.letsencrypt.top) 实现免费SSL证书一键配置和自动续期 一、准备工作 服务器要求 Linux 系统&#xff08;推荐 Ubuntu/CentOS&#xff09;已安装 curl 和 crontab拥有 sudo 权限的用户 域名验证 确保域…

【NLP】自然语言项目设计

目录 项目简介 要求 需要考虑的问题 硬件需求和环境配置 n卡驱动配置 以cuda11.8 版本为例 下载对应的cudnn(version11) 安装GPU版本的torch 安装gpu版本的TensorFlow 检查cuda安装情况 项目简介 训练一个模型&#xff0c;实现歌词仿写生成 任务类型&#xff1a;文本…

设计模式:观察者模式 (Observer) 案例详解

目录 一、引言&#xff1a;为什么需要观察者模式&#xff1f; 二、观察者模式的核心原理 1. 角色划分 2. 类图关系 三、经典案例解析 案例1&#xff1a;天气监测系统 案例2&#xff1a;股票价格监控系统 案例3&#xff1a;MVC架构中的模型-视图分离 案例4&#xff1a;J…

CTF-Misc:开启全方位解题之旅

目录 一、CTF-Misc 入门指引二、基础技能储备2.1 文件格式识别2.2 基础工具使用 三、信息搜集技巧3.1 搜索引擎技巧3.2 网络信息挖掘 四、编码转换奥秘4.1 常见编码类型4.2 编码转换工具 五、隐写分析秘籍5.1 图片隐写5.1.1 LSB 隐写5.1.2 颜色通道与 Exif 信息5.1.3 图片修复与…

Adobe创意套件深度挖掘:效率倍增与灵感迸发的新玩法

最近在深入体验奥地利Blueskyy艺术学院的Adobe正版教育订阅&#xff0c;并研究全家桶时有不少新发现&#xff0c;忍不住想和大家分享一下。 先简单说下这个订阅的感受&#xff1a; Firefly 积分。 这应该是我用过Firefly积分最多的版本&#xff0c;1500点/周。对于我们这些创意…

左神算法之有序二维矩阵中的目标值查找

有序二维矩阵中的目标值查找 目录 有序二维矩阵中的目标值查找1. 题目描述2. 问题解释3. 解决思路方法一&#xff1a;逐行二分查找&#xff08;适合行数较少的情况&#xff09;方法二&#xff1a;利用行列有序特性&#xff08;最优解&#xff09; 4. 代码实现5. 总结 1. 题目描…

深入理解AVL树及其旋转操作

AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单枝树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种方法…

URL带有中文会引入哪些问题

处理含中文字符的 URL 1 为什么会出现“乱码”或崩溃&#xff1f; URL 标准&#xff08;RFC 3986&#xff09;规定&#xff1a;除少数保留字符外&#xff0c;URL 只能包含 ASCII。中文属于 Unicode&#xff0c;因此必须先转换。如果直接把 https://example.com/路径/ 这样的字…

结构体字段能否单独加 mut

你问的这个问题在 Rust 里很常见&#xff1a; 一、结构体字段能否单独加 mut 1. 结构体字段能否单独加 mut&#xff1f; 不能。Rust 中&#xff0c;mut 是用来修饰变量绑定的&#xff0c;可变性是绑定的属性&#xff0c;而不是结构体字段本身的属性。 你不能写&#xff1a; …

scGPT-spatial 复现

文章目录 ✅ 总体流程总览&#xff08;从 H5AD 到模型训练&#xff09;&#x1f527; 步骤 1&#xff1a;读取 H5AD 文件并做基础预处理&#x1f9f1; 步骤 2&#xff1a;构造训练样本输入&#xff08;token、value&#xff09;&#x1f4e6; 步骤 3&#xff1a;使用 DataColla…

运放电压跟随器为什么要加电阻

运放电压跟随器为什么要加电阻 我们常见运放的电压跟随器如下&#xff1a; 有时候会看见电路中加两个电阻&#xff1a; 作用就是保护运放&#xff0c;起限流电阻的作用。 当输入电压高的时候&#xff0c;运放内部存在钳位二极管&#xff0c;此电阻就能限流。 并不是所有运放…

MinerU 2.0部署

简介 MinerU 2.0使用sglang加速&#xff0c;与之前差别较大&#xff0c;建议按照官方的Docker镜像的方式启动。 Docker镜像 Dockerfile 这是官方的Dockerfile # Use the official sglang image FROM lmsysorg/sglang:v0.4.7-cu124# install mineru latest RUN python3 -m …

黑马python(十七)

目录&#xff1a; 1.数据可视化-地图-基础案例 2.全国疫情地图 3.河南省疫情地图绘制 4.基础柱状图构建 5.基础时间线柱状图绘制 6.动态GDP柱状图绘制 1.数据可视化-地图-基础案例 图示有点对的不准&#xff0c;可以通过后面的参数 2.全国疫情地图 3.河南省疫情地图绘制…

Segment Anything in High Quality之SAM-HQ论文阅读

摘要 最近的 Segment Anything Model(SAM)在扩展分割模型规模方面取得了重大突破,具备强大的零样本能力和灵活的提示机制。尽管 SAM 在训练时使用了 11 亿个掩码,其掩码预测质量在许多情况下仍不理想,尤其是对于结构复杂的目标。我们提出了 HQ-SAM,使 SAM 能够精确地分割…

深入理解_FreeRTOS的内部实现(2)

1.事件组 事件组结构体&#xff1a; 事件组 “不关中断” 的核心逻辑 事件组操作时&#xff0c;优先选择 “关调度器” 而非 “关中断” &#xff0c;原因和实现如下&#xff1a; 关调度器&#xff08;而非关中断&#xff09; FreeRTOS 提供 taskENTER_CRITICAL()&#xff08;…

【图论题典】Swift 解 LeetCode 最小高度树:中心剥离法详解

文章目录 摘要描述题解答案题解代码分析思路来源&#xff1a;树的“中心剥离法”构造邻接表和度数组循环剥叶子终止条件 示例测试及结果时间复杂度空间复杂度总结 摘要 树是一种重要的数据结构&#xff0c;在许多应用里&#xff0c;我们希望选一个根&#xff0c;让这棵树的高度…

Docker的介绍与安装

​ Docker 对初学者的简单解释和应用场景 1.什么是 Docker&#xff1f; 简单来说&#xff0c;Docker 就像一个“装箱子”的工具&#xff0c;这个箱子叫做“容器”。 你写的程序和它运行需要的环境&#xff08;比如操作系统、软件、工具&#xff09;都装进一个箱子里。这个箱…

引导相机:工业自动化的智能之眼,赋能制造业高效升级

在工业自动化浪潮中&#xff0c;精准的视觉引导技术正成为生产效率跃升的关键。作为迁移科技——一家成立于2017年、专注于3D工业相机和3D视觉系统的领先供应商&#xff0c;我们深知"引导相机"的核心价值&#xff1a;它不仅是一个硬件设备&#xff0c;更是连接物理世…

智能相机如何重塑工业自动化?迁移科技3D视觉系统的场景革命

从硬件参数到产业价值&#xff0c;解码高精度视觉系统的落地逻辑 一、工业视觉的“智慧之眼” 迁移科技深耕3D工业相机领域&#xff0c;以“稳定、易用、高回报”为核心理念&#xff0c;打造覆盖硬件、算法、软件的全栈式视觉系统。成立6年累计融资数亿元的背后&#xff0c;是…