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

设计模式 17 组合模式 Composite Pattern

设计模式 17 组合模式 Composite Pattern

1.定义


组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。它允许你将对象组合成树形结构,以表示“部分-整体”层次关系。它将单个对象和组合对象都视为相同类型的对象,从而使你能够统一地处理它们

核心思想:

将单个对象和组合对象都视为相同类型的对象,即它们都实现相同的接口或抽象类。
组合对象可以包含其他对象,形成树形结构。
客户端代码可以统一地处理单个对象和组合对象。

2.内涵

组合模式的内涵在于它提供了一种 统一处理单个对象和组合对象 的方法,从而简化代码、提高可扩展性和可重用性。

核心内涵

  • 树形结构: 组合模式的核心是构建一个树形结构,以表示“部分-整体”层次关系。树的节点可以是单个对象(叶子节点)或组合对象(非叶子节点)。
  • 统一接口: 组合模式要求所有组件(包括单个对象和组合对象)都实现相同的接口或抽象类。这使得客户端代码可以统一地处理所有组件,而无需关心它们是单个对象还是组合对象。
  • 递归操作: 组合模式通常使用递归来处理组合对象。当对组合对象进行操作时,它会递归地对它的子组件进行相同操作。
  • 简化代码: 由于所有组件都具有相同的接口,客户端代码可以统一地处理它们,避免了对不同类型对象的特殊处理。
  • 提高可扩展性: 可以轻松地添加新的组件类型,而无需修改现有代码。因为新的组件只需要实现相同的接口即可。
  • 增强灵活性和可重用性: 可以灵活地组合不同的组件,以创建不同的结构,并可以将这些结构重用在不同的场景中


3.案例分析

#include <algorithm>
#include <iostream>
#include <list>
#include <string>class Component {/*** @var Component*/protected:Component *parent_;public:virtual ~Component() {}void SetParent(Component *parent) {this->parent_ = parent;}Component *GetParent() const {return this->parent_;}virtual void Add(Component *component) {}virtual void Remove(Component *component) {}virtual bool IsComposite() const {return false;}virtual std::string Operation() const = 0;
};class Leaf : public Component {public:std::string Operation() const override {return "Leaf";}
};class Composite : public Component {protected:std::list<Component *> children_;public:void Add(Component *component) override {this->children_.push_back(component);component->SetParent(this);}void Remove(Component *component) override {children_.remove(component);component->SetParent(nullptr);}bool IsComposite() const override {return true;}std::string Operation() const override {std::string result;for (const Component *c : children_) {if (c == children_.back()) {result += c->Operation();} else {result += c->Operation() + "+";}}return "Branch(" + result + ")";}
};void ClientCode(Component *component) {// ...std::cout << "RESULT: " << component->Operation();// ...
}void ClientCode2(Component *component1, Component *component2) {// ...if (component1->IsComposite()) {component1->Add(component2);}std::cout << "RESULT: " << component1->Operation();// ...
}int main() {Component *simple = new Leaf;std::cout << "Client: I've got a simple component:\n";ClientCode(simple);std::cout << "\n\n";Component *tree = new Composite;Component *branch1 = new Composite;Component *leaf_1 = new Leaf;Component *leaf_2 = new Leaf;Component *leaf_3 = new Leaf;branch1->Add(leaf_1);branch1->Add(leaf_2);Component *branch2 = new Composite;branch2->Add(leaf_3);tree->Add(branch1);tree->Add(branch2);std::cout << "Client: Now I've got a composite tree:\n";ClientCode(tree);std::cout << "\n\n";std::cout << "Client: I don't need to check the components classes even when managing the tree:\n";ClientCode2(tree, simple);std::cout << "\n";delete simple;delete tree;delete branch1;delete branch2;delete leaf_1;delete leaf_2;delete leaf_3;return 0;
}

以上代码UML图如下所示:


4.注意事项


在使用组合模式进行开发时,需要考虑以下几个注意事项:

1. 避免循环引用:

组合模式中,组件之间可以相互嵌套,形成树形结构。如果出现循环引用,会导致无限递归,最终导致程序崩溃。
例如,文件夹 A 包含文件夹 B,文件夹 B 又包含文件夹 A,就会形成循环引用。
避免循环引用的方法是仔细设计组件之间的关系,确保没有相互依赖的循环。
2. 谨慎使用递归:

组合模式中,通常使用递归来处理组合对象。递归虽然方便,但可能会导致栈溢出,尤其是在处理大型树形结构时。
为了避免栈溢出,可以考虑使用迭代的方式来代替递归,或者使用尾递归优化。
3. 考虑性能:

组合模式中,对组合对象的访问可能会涉及多个子组件的访问,因此需要考虑性能问题。
为了提高性能,可以考虑使用缓存机制,或者使用更轻量级的结构来代替树形结构。
4. 确保接口的完整性:

组合模式中,所有组件都必须实现相同的接口。因此,需要确保接口的完整性,包含所有必要的操作方法。
接口应该尽可能地抽象,避免与具体实现细节相关联。
5. 避免过度使用:

组合模式是一种强大的模式,但它并不适合所有场景。如果你的系统结构比较简单,或者没有明显的“部分-整体”层次关系,则不需要使用组合模式。
在选择设计模式时,需要权衡利弊,选择最适合的模式


5.最佳实践

组合模式是一个强大的工具,但要有效地运用它,需要遵循一些最佳实践:

1. 明确“部分-整体”层次关系:

首先,要确保你的系统中存在明显的“部分-整体”层次关系。例如,文件系统中的文件夹和文件,组织结构中的部门和员工,图形界面中的容器和组件等。
只有在存在这种层次关系的情况下,组合模式才能发挥其优势。
2. 设计清晰的组件接口:

定义一个抽象的 Component 接口,所有组件(包括单个对象和组合对象)都必须实现这个接口。
接口应该包含所有必要的操作方法,例如 add(), remove(), getChild(), getName(), getSize() 等,这些方法应该能够适用于所有类型的组件。
3. 确保接口的完整性:

接口应该尽可能地抽象,避免与具体实现细节相关联。
同时,接口应该包含所有必要的操作方法,以支持所有可能的用例。
4. 谨慎使用递归:

递归是处理组合对象的一种常见方式,但它可能会导致栈溢出,尤其是在处理大型树形结构时。
可以考虑使用迭代的方式来代替递归,或者使用尾递归优化。
5. 考虑性能:

在处理大型树形结构时,性能是一个重要因素。
可以考虑使用缓存机制,或者使用更轻量级的结构来代替树形结构。
6. 避免过度使用:

组合模式并不适合所有场景。如果你的系统结构比较简单,或者没有明显的“部分-整体”层次关系,则不需要使用组合模式。
在选择设计模式时,需要权衡利弊,选择最适合的模式。
7. 使用示例代码进行验证:

在实际应用中,可以使用示例代码来验证组合模式的实现是否符合预期。
通过测试用例,可以确保组合模式能够正确地处理各种情况。


6.总结


组合模式的内涵在于它通过统一接口和递归操作,将单个对象和组合对象统一起来,简化了代码,提高了可扩展性和可重用性。它为构建灵活、可扩展和可重用的树形结构提供了强大的支持。
 

相关文章:

设计模式 17 组合模式 Composite Pattern

设计模式 17 组合模式 Composite Pattern 1.定义 组合模式&#xff08;Composite Pattern&#xff09;&#xff0c;又叫部分整体模式&#xff0c;是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次。这种类型的设…...

【经典论文阅读10】MNS采样——召回双塔模型的最佳拍档

这篇发表于2020 WWW 上的会议论文&#xff0c;提出一种MNS方式的负样本采样方法。众所周知&#xff0c;MF方法难以解决冷启动问题&#xff0c;于是进化出双塔模型&#xff0c;但是以双塔模型为基础的召回模型的好坏十分依赖负样本的选取。为了解决Batch内负样本带来的选择性偏差…...

串行低功耗芯片间媒体总线(SLIMbus)介绍

文章目录 SLIMbus简介slimbus设备和设备类Manager DeviceFramer DeviceInterface DeviceGeneric Device (Function)SLIMbus组件简单的SLIMbus组件复杂的SLIMbus组件SLIMbus的DATA和CLKSLIMbus的Clock Frequencies和GearsCells, Slots, Subframes, Frames, and...

esp32-S3 使用自带的大模型,实现本地文字转语言tts

目录 CMakeLists.txt文件中: 初始化以及实际运用代码: 在partitions.csv 内存分配文件中,添加voice_data项...

Redis事务(1)

什么是事务&#xff1f; Redis 的事务和 MySQL 的事务概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执行。 但是注意体会 Redis 的事务和 MySQL 事务的区别: 弱化的原⼦性: redis 没有 “回滚机制”. 只能做到这些操作 “批量执⾏”. 不能做到 “⼀个失败就…...

202206青少年软件编程(Python)等级考试试卷(四级)

第 1 题 【单选题】 有如下 Python 程序, 包含 lambda 函数, 运行该程序后, 输出的结果是? ( ) g = lambda x,y:x*yprint(g(2,3))A :2 B :3 C :6 D :8 正确答案:C 试题解析: g = lambda x, y: x*y, lambda 函数返回参数 x 和 y 的积, 因此选 C。 第 2 题 【单选…...

大作业爬取手机数据,实现手机推荐系统以及朋友圈手机论坛

1、功能简介 &#xff08;1&#xff09;用户注册与用户登录 &#xff08;2&#xff09;手机搜索、手机比拼、手机个性化推荐 &#xff08;3&#xff09;点击搜索的手机图片会就用户行为&#xff0c;轮播展示用户行为&#xff0c;推荐点击次数靠前的手机 &#xff08;4&#xf…...

Leetcode 环形链表|| 快慢指针解法

但是我们不知道 aaa 的值&#xff0c;该怎么办&#xff1f;依然是使用双指针法。考虑构建一个指针&#xff0c;此指针需要有以下性质&#xff1a;此指针和 slow 一起向前走 a 步后&#xff0c;两者在入口节点重合。那么从哪里走到入口节点需要 aaa 步&#xff1f;答案是链表头节…...

出书,是「盖你自己的房子」你知道吗?

出书是「盖你自己的房子」 尊敬的出书盟友&#xff1a; 你好&#xff01;我希望这封信能够激发您对出书和阅读的热情。 在当今信息爆炸的时代&#xff0c;每个人都有机会分享自己的故事、思想和知识。而书籍作为一种流传百年的媒体&#xff0c;依旧承载着无限的力量和影响力…...

深入探索MySQL SELECT查询:从基础到高级,解锁数据宝藏的密钥

系列文章目录 更新ing... MySQL操作全攻略&#xff1a;库、表、数据、事务全面指南深入探索MySQL SELECT查询&#xff1a;从基础到高级&#xff0c;解锁数据宝藏的密钥MySQL SELECT查询实战&#xff1a;练习题精选&#xff0c;提升你的数据库查询技能PyMySQL&#xff1a;连接P…...

驾校管理系统-手把手调试搭建

驾校管理系统-手把手调试搭建 驾校管理系统-手把手调试搭建...

知能行——考研数学利器

知能行使用体验全记录 首先&#xff0c;我先介绍一下自己&#xff0c;我是2018级的&#xff0c;2022年6月毕业&#xff0c;本科沈阳工业大学&#xff08;双非&#xff09;&#xff0c;今年二战&#xff0c;专业课自动控制原理&#xff0c;数二英二&#xff0c;目标是江南大学控…...

pod 库发布脚本

repo_tag.sh 文件 #!/bin/zsh# 私有库名称 #PODNAME${PWD##*/} PODNAME"LBHorizontalCenterLayout"function obtain_git_tag {# 类似 "s.version 0.0.1"VERSION_STRINGgrep -E s.version.* ${PODNAME}.podspecTAGtr -cd "[0-9.]" <<&…...

Java 8 新特性:深入理解 Lambda 表达式的强大与应用

Java 8 新特性&#xff1a;深入理解 Lambda 表达式的强大与应用 Lambda 表达式是 Java 8 引入的重要特性之一&#xff0c;它允许将匿名函数&#xff08;即无名称的函数&#xff09;作为参数传递给方法&#xff0c;简化了代码的编写&#xff0c;使代码更加简洁和易读。本文将深…...

HTML5 Canvas图形绘制技术应用

HTML5 Canvas图形绘制技术应用 目录 Canvas基础知识基本绘图操作路径操作文本绘制图像绘制变换复合图形与剪切阴影渐变动画与交互高级技巧...

JMETER工具:以录制手机app为例

JMETER工具&#xff1a;以录制手机app为例子 JMETER安装和环境配置 pc需要安装jdk&#xff0c;并进行jdk的环境配置&#xff0c;安装好jdk并配置好后&#xff0c;通过命令行输入java –version出现以下界面就表示安装成功&#xff1a; &#xff08;对应的jdk版本不可太低&…...

PDF文件权限密码保护:如何去除及解决方法

如果你忘记了PDF文件密码&#xff0c;不用担心&#xff01;PDF解密、找回密码、去除密码的方法简单易行。只需两步&#xff1a;1、打开百度搜索“密码帝官网”&#xff1b;2、在官网页面点击“立即开始”&#xff0c;上传文件&#xff0c;稍等片刻即可找回密码。这种方法安全、…...

【电子信息(工程)】电子通信创新创业教育综合

电子通信创新创业教育 阐述电磁场、电磁波和电磁频谱及应用一、电磁场 法拉第根据电流与磁场的关系,提出了电磁感应定律:如果电磁场中有处于运动状态下的闭合回路导体存在,流经该导体磁场的磁场强度和磁场量,通常都会出现相应的变化,电磁感应电流由此而产生。随后,英国的…...

光伏无人机巡检的工作原理是什么?

随着科技的飞速发展&#xff0c;无人机技术已经深入到众多领域&#xff0c;其中光伏电站的巡检工作便是其应用的一个重要方向。光伏无人机巡检&#xff0c;通过搭载各种先进的传感器和设备&#xff0c;对光伏电站进行全面的、高效的、安全的检测&#xff0c;为电站的运维管理提…...

泛型中K T V E ? Object等分别代表的含义

E – Element (在集合中使用&#xff0c;因为集合中存放的是元素) T – Type&#xff08;Java 类&#xff09; K – Key&#xff08;键&#xff09; V – Value&#xff08;值&#xff09; N – Number&#xff08;数值类型&#xff09; &#xff1f; – 表示不确定的java类型&…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...