当前位置: 首页 > news >正文

设计模式21-组合模式

设计模式21-组合模式(Composite Pattern)

  • 写在前面
  • 动机
  • 定义与结构
    • 定义
    • 结构
      • 主要类及其关系
  • C++代码推导
  • 优缺点
  • 应用场景
  • 总结
  • 补充
    • 叶子节点不重载这三个方法
    • 叶子节点重载这三个方法
    • 结论

写在前面

数据结构模式
常常有一些组件在内部具有特定的数据结构。如何让客户程序依赖这些特定的数据结构,将极大的破坏组件的复用。那么这个时候将这些特定数据结构封装在内部。在外部提供统一的接口来实现与特定数据结构无关的访问。是一种行之有效的解决方案。

典型模式

  • 组合模式
  • 迭代器模式

动机

  • 软件在某些情况下,客户代码过多的依赖于对象容器复杂的内部实现结构,对象容器内部实现结构而非抽象接口的变化将引起客户代码的频繁变化。代码的维护性,扩展性等弊端。
  • 那么如何将客户代码与复杂的对象容器结构进行解耦?让对象容器自己来实现自身的复杂结构。而使得客户代码就像处理简单对象一样来实现处理复杂的对象容器?
  • 在软件开发中,有时我们需要处理树形结构的数据。例如,图形编辑器中一个复杂图形可以由多个简单图形(如线条、圆形、矩形等)组合而成。无论是单个简单图形还是复杂图形的组合,从操作上看,它们应当被视为一个整体。组合模式的动机是通过将对象组合成树形结构来表示“部分-整体”的层次结构,使得客户端可以一致地处理单个对象和组合对象。

定义与结构

定义

组合模式允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。

结构

在这里插入图片描述

这张图是一个UML(统一建模语言)类图,用于展示软件系统中类之间的结构和关系。通过图形化的方式描述了类的属性、操作(方法)以及类之间的继承、关联等关系。

主要类及其关系

  1. Client(客户端)

    • 继承自Component类。
    • 表示一个使用组件的客户端实体。客户端通过继承Component类,获得了对子节点的操作能力,包括添加、删除和获取子节点。
  2. Component(组件)

    • 是一个抽象类,代表了一个具有子组件概念的通用组件。
    • 它定义了三个操作(方法):
      • Add(Component): 添加一个子组件。
      • Remove(Component): 移除一个子组件。
      • GetChild(int): 根据索引获取子组件。
    • 还有一个属性children,用于存储子组件的集合,尽管这个属性在图中没有明确标出,但根据UML的惯例和类的操作可以推断出来。
  3. Leaf(叶子节点)

    • 继承自Component类。
    • 表示没有子节点的组件,即树的叶子。
    • 它同样定义了操作列表,但这里特别指出了一个forall g in children的操作,这实际上是一个伪代码或注释,因为叶子节点没有子节点(children为空或不存在),所以这个操作在叶子节点上下文中不适用。这里的展示可能只是为了强调Leaf类继承自Component类,并保留了Component的接口结构。
  4. Composite(复合节点)

    • 继承自Component类。
    • 表示具有多个子组件的复合结构,如树中的非叶子节点。
    • 它除了具有Component类定义的操作外,还特别指出了对子节点g的操作(g.Operation():),这里g代表了一个子组件的实例,这个注释或伪代码表明Composite类可以对其子节点执行某种操作,但没有具体说明是什么操作,这取决于实际的应用场景。

这张UML类图展示了一个典型的组合模式(Composite Pattern)的结构,其中Component是一个抽象类,代表了一个具有共同接口的对象,这个接口允许在组件的单个对象和组合对象之间进行一致的操作。Client类展示了如何使用这个结构,而LeafComposite类则分别代表了结构中的叶子节点和复合节点。通过这种方式,系统可以以统一的方式处理单个对象和组合对象,简化了客户端代码并提高了系统的可扩展性。

C++代码推导

以下是一个使用组合模式的C++代码示例,模拟一个文件系统,其中目录可以包含文件或其他子目录。

抽象组件类:

#include <iostream>
#include <vector>
#include <string>// 抽象组件类,表示文件系统的节点
class FileSystemComponent {
public:virtual void showDetails(int indent = 0) const = 0;virtual void add(FileSystemComponent* component) {throw std::runtime_error("Cannot add to a leaf component");}virtual void remove(FileSystemComponent* component) {throw std::runtime_error("Cannot remove from a leaf component");}virtual ~FileSystemComponent() = default;
};

叶子节点类(文件):

class File : public FileSystemComponent {
private:std::string name;public:File(const std::string& name) : name(name) {}void showDetails(int indent = 0) const override {std::cout << std::string(indent, ' ') << name << std::endl;}
};

组合节点类(目录):

class Directory : public FileSystemComponent {
private:std::string name;std::vector<FileSystemComponent*> components;public:Directory(const std::string& name) : name(name) {}void add(FileSystemComponent* component) override {components.push_back(component);}void remove(FileSystemComponent* component) override {components.erase(std::remove(components.begin(), components.end(), component), components.end());}void showDetails(int indent = 0) const override {std::cout << std::string(indent, ' ') << name << "/" << std::endl;for (const auto& component : components) {component->showDetails(indent + 2);}}~Directory() {for (auto component : components) {delete component;}}
};

客户端代码:

int main() {FileSystemComponent* rootDir = new Directory("root");FileSystemComponent* homeDir = new Directory("home");FileSystemComponent* userDir = new Directory("user");FileSystemComponent* file1 = new File("file1.txt");FileSystemComponent* file2 = new File("file2.txt");FileSystemComponent* file3 = new File("file3.txt");rootDir->add(homeDir);homeDir->add(userDir);userDir->add(file1);userDir->add(file2);homeDir->add(file3);rootDir->showDetails();delete rootDir;return 0;
}

运行结果:

root/home/user/file1.txtfile2.txtfile3.txt

优缺点

优点:

  1. 统一性:组合模式使得客户端可以一致地处理单个对象和组合对象,统一了对叶子节点和组合节点的操作。
  2. 灵活性:可以很方便地增加新的节点类型(如新的文件类型或目录类型),符合开闭原则。
  3. 简化客户端代码:客户端无需关心处理的是单个对象还是组合对象,减少了代码复杂性。

缺点:

  1. 复杂性:可能会导致系统中类的数量增加,特别是当需要支持复杂的树形结构时。
  2. 难以限制组合:在组合模式中,很难限制哪些组件可以组合在一起,容易导致不合理的组合结构。

应用场景

组合模式在以下场景中应用较多:

  1. 需要表示树形结构的场景:如文件系统、组织结构、UI组件树等。
  2. 需要统一处理单个对象和组合对象的场景:如图形编辑器中的简单图形和组合图形。
  3. 需要动态构建部分-整体结构的场景:如菜单和子菜单的构建,产品配置和子组件的构建。

总结

  • 组合模式通过将对象组合成树形结构来表示“部分-整体”的层次结构,使得客户端可以一致地处理单个对象和组合对象。它在需要处理树形结构的数据时非常有效,能够简化客户端代码,并提供很好的扩展性。然而,由于可能引入更多的类,特别是当系统的组合结构复杂时,需要注意管理组合的复杂性。
  • 组合模式采用树形结构来实现普遍存在的对象容器,从而将一对多的关系转化为一对一的关系。使得客户代码可以一致的复用处理对象和和对象容器。无需关心处女的是单个对象还是组合的对象容器
  • 客户代码与复杂的对象容器结构解耦是组合模式的核心思想。解耦之后,客户代码将与纯粹的抽象接口而非对象容器的内部时间结构发生依赖,从而更能应对变化。
  • 组合模式在具体的实现中可以让父对象中的子对象反向追溯。如果富对线有频繁的便利需求,可以使用缓存技巧来改善效率。

补充

在组合模式中,叶子节点通常不需要实现(即重载)Add(Component), Remove(Component), GetChild(int)这三个方法,因为叶子节点不包含子节点。这些方法主要用于组合节点(Composite)以便管理子节点。但有时,为了简化代码或提高灵活性,叶子节点也可能会实现这些方法。以下是叶子节点重载与不重载这三个方法的优缺点对比。

叶子节点不重载这三个方法

实现方式:

在叶子节点中,这些方法通常被声明但不实现(在C++中通常可以抛出异常或者是空实现)。叶子节点不需要管理子组件。

class Leaf : public Component {
public:void Add(Component* component) override {throw std::runtime_error("Leaf nodes do not support Add operation");}void Remove(Component* component) override {throw std::runtime_error("Leaf nodes do not support Remove operation");}Component* GetChild(int index) override {throw std::runtime_error("Leaf nodes do not support GetChild operation");}void Operation() override {// 具体叶子节点的操作实现}
};

优点:

  1. 清晰的语义:叶子节点明确不支持子节点管理操作,这使得代码的意图更加清晰,避免了误用。
  2. 更强的类型安全:由于明确抛出异常或不实现,可以在运行时捕捉到错误,而不是让无意义的操作通过。
  3. 符合职责分离原则:叶子节点只专注于具体操作,不需要处理与子节点相关的逻辑。

缺点:

  1. 客户端代码需要做额外的检查:客户端需要知道一个组件是否是叶子节点,以避免调用不支持的方法,可能增加了客户端的复杂性。
  2. 减少了一致性:对客户端来说,调用这些方法会抛出异常或导致错误,这可能会影响代码的一致性和简洁性。

叶子节点重载这三个方法

实现方式:

叶子节点实现(重载)这些方法,但不执行任何操作或返回特定值,如nullptr

class Leaf : public Component {
public:void Add(Component* component) override {// 叶子节点不支持添加操作,但实现了这个方法}void Remove(Component* component) override {// 叶子节点不支持移除操作,但实现了这个方法}Component* GetChild(int index) override {return nullptr; // 叶子节点没有子节点,返回空指针}void Operation() override {// 具体叶子节点的操作实现}
};

优点:

  1. 简化客户端代码:客户端代码不需要检查节点类型,可以统一调用Add, Remove, GetChild,简化了代码逻辑。
  2. 提高一致性:所有组件(叶子节点和组合节点)都实现了相同的接口,提供了一致的编程接口。
  3. 增加灵活性:在未来扩展时,如果叶子节点需要支持子节点管理,可以直接扩展已有方法。

缺点:

  1. 隐藏潜在错误:叶子节点实现了不应该执行的操作(如AddRemove),可能导致误用而不易发现。
  2. 不符合职责分离原则:叶子节点本不应该涉及子节点管理操作,实现这些方法可能违反单一职责原则。
  3. 占用资源:虽然通常影响很小,但实现这些无操作的方法也会占用一些资源(例如代码空间),特别是在资源受限的环境中。

结论

  1. 不重载方法的情况:适用于严格遵循职责分离原则的场景。通过不重载方法,明确区分了叶子节点和组合节点的职责,使得代码更清晰,类型安全性更高。这种方式适合对系统稳定性和安全性要求较高的场合,或在需要明确捕获误用场景的应用中使用。

  2. 重载方法的情况:适用于追求客户端代码简单性和一致性的场景。通过重载这些方法,客户端不需要区分叶子节点和组合节点,统一处理所有组件,减少了代码的复杂性。这种方式适合在系统中灵活性要求较高、且不易出错的场合。

综上,选择是否重载这些方法取决于具体应用的需求、开发团队的编码习惯和系统的复杂性。如果系统需要严格的职责区分和类型安全性,建议不重载这些方法;如果系统追求统一性和简洁性,可以考虑重载这些方法。

相关文章:

设计模式21-组合模式

设计模式21-组合模式&#xff08;Composite Pattern&#xff09; 写在前面 动机定义与结构定义结构主要类及其关系 C代码推导优缺点应用场景总结补充叶子节点不重载这三个方法叶子节点重载这三个方法结论 写在前面 数据结构模式 常常有一些组件在内部具有特定的数据结构。如何…...

如何选择深度学习的损失函数和激活函数

一概述 在深度学习中&#xff0c;损失函数&#xff08;Loss Function&#xff09;和激活函数&#xff08;Activation Function&#xff09;是两个至关重要的组件&#xff0c;它们共同影响着模型的训练效果和泛化能力。本文将简要介绍这两个概念&#xff0c;阐述选择它们的重要性…...

DATAX自定义KafkaWriter

因为datax目前不支持写入数据到kafka中&#xff0c;因此本文主要介绍如何基于DataX自定义KafkaWriter&#xff0c;用来同步数据到kafka中。本文偏向实战&#xff0c;datax插件开发理论宝典请参考官方文档&#xff1a; https://github.com/alibaba/DataX/blob/master/dataxPlug…...

Mybatis分页多表多条件查询

个人总结三种方式&#xff1a; Xml、queryWrapper、PageHelper第三方组件这三种方式进行查询&#xff1b; 方式一&#xff1a; xml中联表查询&#xff0c;在mapper中传参IPage<T>和条件Map&#xff08;这里用map装参数&#xff09;。 代码示例&#xff1a; Mapper层 M…...

SpringBoot快速入门(手动创建)

目录 案例&#xff1a;需求 步骤 1 创建Maven项目 2 导入SpringBoot起步依赖 3 定义Controller 4 编写引导类 案例&#xff1a;需求 搭建简单的SpringBoot工程&#xff0c;创建hello的类定义h1的方法&#xff0c;返回Hello SpringBoot! 步骤 1 创建Maven项目 大家&…...

C 408—《数据结构》算法题基础篇—数组(通俗易懂)

目录 Δ前言 一、数组的合并 0.题目&#xff1a; 1.算法设计思想&#xff1a; 2.C语言描述&#xff1a; 3.算法的时间和空间复杂度 : 二、数组元素的倒置 0.题目 : 1.算法设计思想 : 2.C语言描述 : 3.算法的时间和空间复杂度 : 三、数组中特定值元素的删除 0.题目 : …...

AI秘境-墨小黑奇遇记 - 初体验(一)

“怎么可能&#xff01;”墨小黑盯着屏幕上的代码&#xff0c;整个人都不好了。调试了三遍&#xff0c;翻了几遍书&#xff0c;结果还是不对。就像你以为自己早起赶车&#xff0c;结果发现闹钟根本没响一样崩溃。 这是他第一次真正接触人工智能实战任务——实现一个简单的感知…...

文件IO813

标准IO文件定位&#xff1a; fseek函数&#xff1a; 功能&#xff1a;将stream流文件中的文件指针从whence位置开始偏移offset个字节的长度。 int fseek(FILE *stream , long offset, int whence); FILE *stream 指的是所需要定位的文件&#xff08;文化定位前提是文件要被打…...

STP(生成树)的概述和工作原理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...

从AGV到立库,物流自动化的更迭与未来

AGV叉车 随着柔性制造系统的广泛应用&#xff0c;小批量、多批次的生产需求不断增强&#xff0c;“订单导向”生产已经成为趋势。这也让越来越多的企业认识到&#xff0c;产线的智能设备导入只是第一步&#xff0c;要想达到生产效率的最优解&#xff0c;物流系统的再优化必须提…...

阴阳脚数码管

1.小故事 最近&#xff0c;我接到了一个既“清肺”又“烧脑”的新任务&#xff0c;设计一个低功耗蓝牙肺活量计。在这个项目中我们借鉴了一款蓝牙跳绳的硬件设计方案&#xff0c;特别是它的显示方案——数码管。 在电子工程领域&#xff0c;初学者往往从操作LED开始&#xff…...

【Vue3-Typescript】<script setup lang=“ts“> 使用 ref标签 怎么获取 refs子组件呢

注意&#xff1a;请确保子组件已经正确挂载&#xff0c;并且通过 defineExpose 暴露了您想要在父组件中访问的属性或方法 parent.vue <template><child ref"childRef"></child><button click"fun">点击父组件</button> &l…...

npm 超详细使用教程

文章目录 一、简介二、npm安装三、npm 的使用3.1 npm初始化项目3.2 安装包3.3 安装不同版本包3.4 避免系统权限3.5 更新包3.6 卸载包3.7 执行脚本3.8 pre- 和 post- 脚本3.9 npm link3.10 发布和卸载发布的包3.11 使用npm版本控制3.22 npm资源 四、总结 一、简介 npm&#xff…...

TypeScript函数

函数 函数:复用代码块 函数可以不写返回值 调用函数-----函数名() function a(){console.log(无参函数); } a();需要再函数后&#xff0c;写上返回值类型 没有返回值 使用void function e():string{return 可乐 } console.log(我得到了e()); function d():void{console.l…...

中海油某海上平台轨道巡检机器人解决方案

配电房作为能源传输和分配的核心枢纽&#xff0c;其安全运行直接影响到企业的生产稳定性和安全性。对于中海油这样的大型能源企业&#xff0c;配电房的运行状况至关重要。然而&#xff0c;传统的人工巡检方式存在效率低、作业风险高、巡检误差大等问题。为提升巡检效率、降低安…...

【NXP-MCXA153】SPI驱动移植

介绍 SPI总线由摩托罗拉公司开发&#xff0c;是一种全双工同步串行总线&#xff0c;由四个IO口组成&#xff1a;CS、SCLK、MISO、MOSI&#xff1b;通常用于CPU和外设之间进行通信&#xff0c;常见的SPI总线设备有&#xff1a;TFT LCD、QSPI FLASH、时钟模块、IMU等&#xff1b…...

Python if 编程题|Python一对一辅导教学

你好&#xff0c;我是悦创。 以下为 if 编程练习题&#xff1a; 1. 奇数乘积问题 题目描述: 编写一个程序&#xff0c;判断给定的两个整数是否都是奇数&#xff0c;如果是&#xff0c;返回它们的乘积&#xff1b;如果不是&#xff0c;返回它们的和。输入: num1, num2输出: n…...

机器学习——第十一章 特征选择与稀疏学习

11.1 子集搜索与评价 对一个学习任务来说&#xff0c;给定属性集&#xff0c;其中有些属性可能很关键、很有用&#xff0c;另一些属性则可能没什么用.我们将属性称为"特征" (feature) &#xff0c;对当前学习任务有用的属性称为"相关特征" (relevant featu…...

花式表演无人机技术详解

花式表演无人机作为现代科技与艺术融合的典范&#xff0c;以其独特的飞行姿态、绚烂的灯光效果及精准的控制能力&#xff0c;在各类庆典、体育赛事、音乐会等合中展现出非凡的魅力。本文将从以下几个方面对花式表演无人机技术进行详细解析。 1. 三维建模与编程 在花式表演无人…...

服务器那点事--防火墙

Linux服务器那点事--防火墙 Ⅰ、开启关闭Ⅱ、放开端口 Ⅰ、开启关闭 禁止防火墙开机自启systemctl disable firewalld 关闭防火墙systemctl stop firewalld 查看防火墙状态systemctl status firewalldⅡ、放开端口 例如&#xff1a;放开3306端口 设置放开3306端口 [rootbpm2…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

Unity中的transform.up

2025年6月8日&#xff0c;周日下午 在Unity中&#xff0c;transform.up是Transform组件的一个属性&#xff0c;表示游戏对象在世界空间中的“上”方向&#xff08;Y轴正方向&#xff09;&#xff0c;且会随对象旋转动态变化。以下是关键点解析&#xff1a; 基本定义 transfor…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)

错误一&#xff1a;yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因&#xff0c;后面把yaml.safe_dump直接替换成yaml.dump&#xff0c;确实能保存&#xff0c;但出现乱码&#xff1a; 放弃yaml.dump&#xff0c;又切…...