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

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

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

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

相关文章

牛客网SQL进阶135 :每个6/7级用户活跃情况

每个67级用户活跃情况_牛客题霸_牛客网 0 问题描述 基于用户信息表user_info、、试卷作答记录表exam_record、题目练习记录表practice_record&#xff0c;统计 每个6/7级用户总活跃月份数、2021年活跃天数、2021年试卷作答活跃天数、2021年答题活跃天数&#xff0c;结果 按照总…

在linux上架设Web服务器Apache(Ubuntu)

欢迎诸位来阅读在下的博文~ 在这里&#xff0c;在下会不定期发表一些浅薄的知识和经验&#xff0c;望诸位能与在下多多交流&#xff0c;共同努力! 江山如画&#xff0c;客心如若&#xff0c;欢迎到访&#xff0c;一展风采 文章目录 背景1. 安装 Apache2. 启动和检查 Apache 服务…

强烈推荐这三款IOS应用,让你的生活更美好

Dino记账 Dino记账是一款结合了简洁设计和强大功能的记账应用&#xff0c;它通过多维度图表帮助用户轻松掌握金钱流向。应用界面明亮且配色突出&#xff0c;使得记录内容易于阅读&#xff0c;让记账和管理账目变得更加简单。 主要特性&#xff1a; 极简风格与易用性&#xff1…

掌握 Spring Boot + MyBatis-Plus 动态数据源切换,只要5分钟!

数据量猛增&#xff0c;通过动态数据源切换&#xff0c;我们不仅能提高查询效率&#xff0c;还能保证系统的高可用性。 通过将写操作集中在主库&#xff0c;读操作分散到多个从库&#xff0c;可以有效减轻数据库的压力。 在pom.xml中添加以下依赖&#xff1a; xml <depend…

Qt系统机制

Qt系统 Qt文件概述输入输出设备类QFileQFileInfoQt多线程Qt多线程常用API使用Qt多线程 线程安全互斥锁读写锁条件变量信号量 Qt网络QUdpSocketQNetworkDatagram设计一个UDP回显服务器QTcpServerQTcpSocketTcp版本的回显服务器HttpClient核心API Qt 音频Qt视频 Qt文件概述 ⽂件操…

入门Pandas必练习100题基础到进阶|阶级教程2

作者:郭震 51. How to get the row number of the nth largest value in a column? Find the row position of the 5th largest value of column a in df. # input df pd.DataFrame(np.random.randint(1, 30, 30).reshape(10,-1), columnslist(abc)) df# Solution 1# argsort…

HEML+CSS超详细基础知识

一些快捷键 ctrl/ 是注释 ctrld 是选中多个相同字 ctrls保存 altZ自动换行 altshift选中多行 HTML认知 基础认知 html初尝试 HTML页面结构介绍 初次尝试 开始动手写一个网页 先新建一个文件&#xff0c;记得后缀要命名成html 然后shift&#xff01;&#xff0c;就会自动…

《系统架构设计师教程(第2版)》第13章-层次式架构设计理论与实践-01-层次式体系结构概述

文章目录 1. 常用层次是架构2. 层次式架构设计的注意点2.1 污水池反模式2.2 应用变得庞大 本章教材又赘述了一遍架构的定义和层次架构风格的概述&#xff0c;我之前的笔记都写了 架构的定义回看《第7章-系统架构设计基础知识-01-软件架构&#xff08;Software Architecture&…

学习测试15-实战6-根据说明书建工程

CAN协议说明书&#xff1a;含义 一&#xff0c;得到表 1&#xff0c;先建信号 2&#xff0c;建报文&#xff0c;将对应信号拖入其中 3&#xff0c;建节点&#xff0c;将报文添加进TX msg里 调整起始位 数据库建立完成 二&#xff0c;不需要面板&#xff0c;直接导入数据库&…

HTTPS证书价格一年多少钱?如何购买?

目前市面上所有免费一年期HTTPS已经全部下架&#xff0c;付费证书已经成为主流。HTTPS证书的价格受多种因素影响&#xff0c;具体有以下几种&#xff1a; 一、证书类型 单域名证书价格一般在几百元左右&#xff0c;通配符价格高一些&#xff0c;千元以上&#xff0c;多域名价…

《知识点扫盲 · Redis 序列化器》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

米家护眼台灯怎么样?书客、米家、明基三款护眼台灯大PK

市面上出现的护眼台灯款式不得不说真的很多&#xff0c;大家若是想要在护眼台灯这个大市场里选购到一款性价比高、质量过关、口碑好且还真的实用的护眼台灯需要认真做好攻略。所以&#xff0c;我们要有技巧的对这些台灯进行筛选&#xff0c;避开那些三无的、网红品牌、无知名度…

http协议与nginx

动态页面与静态页面的差别&#xff1a; &#xff08;1&#xff09;URL不同 静态⻚⾯链接⾥没有“?” 动态⻚⾯链接⾥包含“&#xff1f;” &#xff08;2&#xff09;后缀不同 (开发语⾔不同) 静态⻚⾯⼀般以 .html .htm .xml 为后缀 动态⻚⾯⼀般以 .php .jsp .py等为后…

【吊打面试官系列-Dubbo面试题】Dubbo SPI 和 Java SPI 区别?

大家好&#xff0c;我是锋哥。今天分享关于 【Dubbo SPI 和 Java SPI 区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; Dubbo SPI 和 Java SPI 区别&#xff1f; JDK SPI JDK 标准的 SPI 会一次性加载所有的扩展实现&#xff0c;如果有的扩展吃实话很耗时&…

Python中的类型注解和静态类型检查使用详解

概要 Python作为一种动态类型语言,其灵活性和易用性使其广受欢迎。然而,动态类型也带来了一些问题,如代码可读性差和运行时错误等。为了提高代码质量和可维护性,Python从3.5版本开始引入了类型注解(Type Hints),并且借助第三方工具可以实现静态类型检查。本文将详细介绍…

Python学生信息管理系统

一、需求分析 学生管理系统应具备的功能 1、添加学生及成绩信息 2、将学生信息保存到文件中 3、修改和删除学生信息 4、查询学生信息 5、根据学生成绩进行排序 6、统计学生的总分 二、系统设计 2.1、学生信息管理系统的系统功能结构&#xff08;7大模块&#xff09; 1、录入…

vue里给img的src绑定数据失效

起因 在v-for遍历数据时想要通过给img的src单向绑定 图片路径时出现问题 解决过程 上网查说是webpack构建时识别不到&#xff0c;直接不单绑数据&#xff0c;写死试试看 解决方案 直接require导入图像文件模块

AI Agent调研--7种Agent框架对比!盘点国内一站式Agent搭建平台,一文说清差别!大家都在用Agent做什么?

代理&#xff08;Agent&#xff09;乃一种智能实体&#xff0c;具备自主环境感知与决策行动能力&#xff0c;旨在达成既定目标。作为个人或组织之数字化替身&#xff0c;AI代理执行特定任务与交易&#xff0c;其核心价值在于简化工作流程&#xff0c;削减繁复性&#xff0c;并有…

MSPM0G3507之电赛小车

一、前言 本文没什么技术分享&#xff0c;纯聊天。以下内容均为笔者的浅薄理解&#xff0c;有不对的地方还请多多包涵。 二、相关配置 主控单元&#xff1a;MSPM0G3507SPTR&#xff08;48角&#xff09; 编译环境&#xff1a;Keil5.33、5.39&#xff08;推荐&#xff09;都可 …

Redisson关键参数含义介绍

一、threads&#xff08;线程池数量&#xff09; 对应executor&#xff08;线程池&#xff09; 默认值: 当前处理核数量 * 2 这个线程池数量被所有RTopic对象监听器&#xff0c;RRemoteService调用者和RExecutorService任务共同共享。 二、nettyThreads &#xff08;Netty线…