AI Chat 设计模式:10. 组合模式
本文是该系列的第八篇,采用问答式的方式展开,问题由我提出,答案由 Chat AI 作出,灰色背景的文字则主要是我的一些思考和补充。
问题列表
- Q.1 给我介绍一下组合模式
- A.1
- Q.2 好的,给我举一个组合模式的例子,使用 c++,要求容易理解
- A.2
- Q.3 在这个例子中,抽象类定义了所有可能的方法,包括文件类中其实不需要用的方法,这是透明组合模式吧?
- A.3
- Q.4 明白了,继续举一下安全组合模式的例子吧
- A.4
- Q.5 不错,对比一下安全组合模式和透明组合模式吧
- A.5
Q.1 给我介绍一下组合模式
A.1
组合模式是一种结构型设计模式,它允许将对象组合成树形结构,以表示“部分-整体”的层次关系。通过组合模式,客户端可以统一对待单个对象和组合对象,而无需进行区分。
在组合模式中,有三种主要角色:
- 抽象构件(Component):它是组合中的对象声明接口,可以包含一些默认实现,比如添加、移除子构件等。它可以是抽象类或者接口。
- 叶子构件(Leaf):它表示叶子节点,即没有子节点的对象。叶子构件实现了抽象构件的接口。
- 组合构件(Composite):它表示有子节点的节点对象,可以添加和移除子节点。组合构件也实现了抽象构件的接口,并提供了管理子节点的操作。
通过将叶子构件和组合构件进行递归组合,可以形成复杂的树状结构。这样,客户端就可以使用统一的方式对待单个对象和组合对象,而无需关心对象的具体类型。
使用组合模式的好处包括:
- 简化客户端代码:客户端无需区分叶子节点和组合节点,可以用统一的方式处理它们。
- 增加新节点方便:如果需要增加新的节点类型,只需要扩展抽象构件接口和相应的实现类即可,不会对现有代码造成影响。
- 简化树的操作:组合模式提供了统一的方式来操作整个树状结构,如遍历、搜索、添加、删除等。
总之,组合模式是一种以树状结构表示对象的模式,通过递归组合构件实现对整体和部分的处理,提供了一种灵活的方式来处理复杂的结构关系。
数据必须是以树形结构组织,才能使用组合模式
Q.2 好的,给我举一个组合模式的例子,使用 c++,要求容易理解
A.2
当然,下面给你一个使用C++实现组合模式的例子:假设我们要实现一个文件系统,其中包含文件和文件夹。文件夹可以包含文件或其他文件夹,而文件不能包含其他文件。
首先,我们定义一个抽象构件(Component)的基类,它包含了一些公共的接口:
class FileSystemComponent {
public:virtual void display() = 0;virtual void add(FileSystemComponent* component) = 0;virtual void remove(FileSystemComponent* component) = 0;
};
这个抽象类,定义了子类所有可能的操作。
然后,我们定义文件(File)类,它是叶子构件(Leaf):
#include <iostream>class File : public FileSystemComponent {
private:std::string name;public:File(std::string name) : name(name) {}void display() override {std::cout << "\tFile: " << name << std::endl;}void add(FileSystemComponent*) override {std::cout << "Cannot add to a file." << std::endl;}void remove(FileSystemComponent*) override {std::cout << "Cannot remove from a file." << std::endl;}
};
文件类,并不需要使用 add() remove() 方法。
接下来,我们定义文件夹(Folder)类,它是组合构件(Composite):
#include <iostream>
#include <vector>class Folder : public FileSystemComponent {
private:std::string name;std::vector<FileSystemComponent*> components;public:Folder(std::string name) : name(name) {}void display() override {std::cout << "Folder: " << name << std::endl;for (auto component : components) {component->display();}}void add(FileSystemComponent* component) override {components.push_back(component);}void remove(FileSystemComponent* component) override {auto it = std::find(components.begin(), components.end(), component);if (it != components.end()) {components.erase(it);}}
};
现在,我们可以使用这些类来构建文件系统的树形结构:
int main() {// 创建文件和文件夹FileSystemComponent* file1 = new File("file1.txt");FileSystemComponent* file2 = new File("file2.txt");FileSystemComponent* folder1 = new Folder("Folder 1");FileSystemComponent* folder2 = new Folder("Folder 2");// 组合文件夹folder1->add(file1);folder2->add(file2);folder2->add(folder1);// 显示文件系统folder2->display();// 清理内存delete folder2;return 0;
}
运行上面的代码,输出将会是:
Folder: Folder 2File: file2.txtFolder: Folder 1File: file1.txt
这个例子展示了如何使用组合模式来构建整个文件系统的树形结构。文件夹可以包含文件或其他文件夹,而文件只能是叶子节点。通过使用统一的方式来处理文件和文件夹,我们可以方便地对文件系统进行操作,如添加、删除和显示等。
我们看看这个例子的类图,所有构建都具有相同的接口。
Q.3 在这个例子中,抽象类定义了所有可能的方法,包括文件类中其实不需要用的方法,这是透明组合模式吧?
A.3
是的,这个例子展示的是透明组合模式。在透明组合模式中,抽象构件类定义了组合中所有可能的方法,包括那些在叶子构件中并不需要的方法。这样做的好处是客户端可以一致地对待所有的对象,无需关心它们的具体类型。
在这个例子中,FileSystemComponent
抽象基类包含了display()
、add()
和remove()
方法,这些方法适用于文件和文件夹。由于文件和文件夹都是FileSystemComponent
的子类,所以客户端可以统一地使用这些方法,而无需区分它们的具体类型。
如果我们将add()
和remove()
这两个方法移除抽象基类,那么它们只能在组合构件(如文件夹)中使用,而在叶子构件(如文件)中不可用。这种称为安全组合模式。但透明组合模式更加灵活,因为它允许客户端像操作组合对象一样操作叶子对象,无需额外的类型检查。
总结起来,透明组合模式提供了更统一、更灵活的方式来操作组合结构,客户端可以使用一致的方法对待所有的对象,而无需关心对象的具体类型。
Q.4 明白了,继续举一下安全组合模式的例子吧
A.4
好的,下面是一个使用安全组合模式的例子:假设我们要构建一个组织结构图,其中包括公司、部门和员工。公司是顶层组合对象,部门是公司的子对象,员工是部门的子对象。员工不再具有添加子对象的能力。
首先,我们定义一个抽象构件(Component)的基类,它包含了共同的接口:
class OrgComponent {
public:virtual void display() = 0;
};
抽象构建,现在只定义公共接口。
然后,我们定义公司(Company)类,作为组合构件(Composite):
#include <iostream>
#include <vector>class Company : public OrgComponent {
private:std::string name;std::vector<OrgComponent*> departments;public:Company(std::string name) : name(name) {}void display() override {std::cout << "Company: " << name << std::endl;for (auto department : departments) {department->display();}}void addDepartment(OrgComponent* component) {departments.push_back(component);}void removeDepartment(OrgComponent* component) {auto it = std::find(departments.begin(), departments.end(), component);if (it != departments.end()) {departments.erase(it);}}
};
接下来,我们定义部门(Department)类,也是组合构件(Composite):
#include <iostream>
#include <vector>class Department : public OrgComponent {
private:std::string name;std::vector<OrgComponent*> employees;public:Department(std::string name) : name(name) {}void display() override {std::cout << "\tDepartment: " << name << std::endl;for (auto employee : employees) {employee->display();}}void addEmployee(OrgComponent* component) {employees.push_back(component);}void removeEmployee(OrgComponent* component) {auto it = std::find(employees.begin(), employees.end(), component);if (it != employees.end()) {employees.erase(it);}}
};
最后,我们定义员工(Employee)类,它是叶子构件(Leaf):
#include <iostream>class Employee : public OrgComponent {
private:std::string name;public:Employee(std::string name) : name(name) {}void display() override {std::cout << "\t\tEmployee: " << name << std::endl;}
};
现在,我们可以使用这些类来构建组织结构图:
int main() {// 创建公司OrgComponent* company = new Company("ABC Company");// 创建部门OrgComponent* sales = new Department("Sales Department");OrgComponent* hr = new Department("HR Department");// 创建员工OrgComponent* employee1 = new Employee("John");OrgComponent* employee2 = new Employee("Jane");OrgComponent* employee3 = new Employee("Alice");// 组合部门和员工static_cast<Company*>(company)->addDepartment(sales);static_cast<Company*>(company)->addDepartment(hr);static_cast<Department*>(sales)->addEmployee(employee1);static_cast<Department*>(sales)->addEmployee(employee2);static_cast<Department*>(hr)->addEmployee(employee3);// 显示组织结构图company->display();// 清理内存delete company;return 0;
}
运行上述代码,输出将会是:
Company: ABC CompanyDepartment: Sales DepartmentEmployee: JohnEmployee: JaneDepartment: HR DepartmentEmployee: Alice
在这个例子中,抽象基类OrgComponent
只定义了display()
方法,而在组合构件(如Company
和Department
)中定义了添加和删除子对象的方法。叶子构件Employee
只需要实现自己所需的方法。
这种安全组合模式使得叶子构件没有暴露不适用的方法,而是将它们限制在组合构件中。这样做可以确保只有组合构件能够添加和删除子对象,而叶子构件无法进行这些操作。
安全的原因,叶子构建无法进行添加和删除操作,不会出现误用的情况。
看看类图,发现抽象类只定义了 display 这一个接口,其他各组件需要的接口只定义在自身类中,与透明组合模式明显不同。
Q.5 不错,对比一下安全组合模式和透明组合模式吧
A.5
安全组合模式和透明组合模式是组合模式的两种不同实现方式,它们在如何定义抽象构件(Component)以及在叶子构件(Leaf)和组合构件(Composite)中如何处理共同的接口方法上有所不同。
-
安全组合模式(Safe Composite Pattern):
- 在安全组合模式中,抽象构件(Component)接口中仅包含叶子构件和组合构件共有的方法,不包含对子构件的添加和删除操作。
- 叶子构件和组合构件的接口都不同,叶子构件只实现自己需要的方法,而组合构件实现了管理子构件的方法。
- 优点:叶子构件不需要实现其它不相关的方法,更加清晰和简洁。
- 缺点:客户端需要通过类型判断来调用适当的方法,不够统一。
-
透明组合模式(Transparent Composite Pattern):
- 在透明组合模式中,抽象构件(Component)接口中包含了所有叶子构件和组合构件共有的方法,包括对子构件的添加和删除操作。
- 叶子构件和组合构件的接口完全一致,具有相同的方法集合。
- 优点:客户端可以一致地对待所有的对象,无需关心对象的具体类型,使用更加简洁和方便。
- 缺点:叶子构件需要实现一些不相关的方法,可能在具体实现中抛出异常或不做任何处理。
选择使用安全组合模式还是透明组合模式取决于具体情况,主要考虑以下因素:
- 一致性需求:如果希望客户端统一对待所有对象,无需关心它们的具体类型和能力,透明组合模式更适合。如果要求叶子构件和组合构件的接口完全一致,使用安全组合模式。
- 功能需求:如果叶子构件和组合构件之间有较大的差异,有许多方法只对组合构件有效,而对叶子构件无意义,使用安全组合模式可以保证叶子构件只需实现自己的功能。
- 设计简洁性:安全组合模式可以更加清晰地表示叶子构件和组合构件之间的差异,每个类只需关注自己需要处理的方法。
总之,安全组合模式和透明组合模式都是组合模式的有效实现方式,根据具体需求和设计考虑选择合适的模式。
相关文章:

AI Chat 设计模式:10. 组合模式
本文是该系列的第八篇,采用问答式的方式展开,问题由我提出,答案由 Chat AI 作出,灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 给我介绍一下组合模式A.1Q.2 好的,给我举一个组合模式的例子,使…...

【Nginx12】Nginx学习:HTTP核心模块(九)浏览器缓存与try_files
Nginx学习:HTTP核心模块(九)浏览器缓存与try_files 浏览器缓存在 Nginx 的 HTTP 核心模块中其实只有两个简单的配置,这一块也是 HTTP 的基础知识。之前我们就一直在强调,学习 Nginx 需要的就是各种网络相关的基础知识&…...

【1】-Locust性能测试工具介绍与安装
Locust介绍 locust是一个开源的压测工具,其官网地址是Locust - A modern load testing framework,通过编写Python代码,可以轻松实现百万级的并发,相对于我们熟悉的Jmeter来说,其对压测机的要求更低,而且使…...

基于拉格朗日-遗传算法的最优分布式能源DG选址与定容(Matlab代码实现)
目录 1 概述 2 数学模型 2.1 问题表述 2.2 DG的最佳位置和容量(解析法) 2.3 使用 GA 进行最佳功率因数确定和 DG 分配 3 仿真结果与讨论 3.1 33 节点测试配电系统的仿真 3.2 69 节点测试配电系统仿真 4 结论 1 概述 为了使系统网损达到最低值&a…...

【已解决】jupyter notebook里已经安装了第三方库,还是提示导入失败
在jupyter notebook中运行Python代码,明明已经安装了第三方库,还是提示导入失败。 以导入pandas库为例,其他库同理: 报错代码: import pandas报错原因: 电脑上存在多个python运行环境(比如&a…...

Mybatis使用collection映射一对多查询分页问题
场景:页面展示列表,需要查询多的字段,和一的字段。并且还要分页。 这时候直接想到的是手写sql。 /*** 标签*/private List<BasicResidentTags> tags;Data TableName("basic_resident_tags") public class BasicResidentTag…...
Linux/Windows路由管理
本文主要介绍如果通过linux/Windows命令添加IPV6地址,查看添加IPV6默认路由,查看IPV6邻居缓存 一、Linux 1、查看地址 IPV4: route netstat -route ip route IPV6: ip -6 route show route -A inet6 route -62、添加IPV6地址 ip -6 addr add <…...
openpnp - 设备矫正的零碎记录
文章目录 openpnp - 设备矫正的零碎记录概述笔记设备内部不能有任何强干扰源相机就选100W像素的就行, 没有特殊要求openpnp软件的选择视觉归位必须禁止轴的赤隙矫正不用做运行openpnp软件的计算机, 必须是台式机校验完成后, 数据占用的体积END openpnp - 设备矫正的零碎记录 概…...

Linux内核中的链表、红黑树和KFIFO
lLinux内核代码中广泛使用了链表、红黑树和KFIFO。 一、 链表 linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存放的,因此不需要占用连…...

【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动
[导读]本系列博文内容链接如下: 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动 在【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值一文中介绍了如何利用…...
Android 设备兼容性使用(详细版)
经典好文推荐,通过阅读本文,您将收获以下知识点: 一、设备兼容性分类 二、硬件设备兼容 三、软件 APP 兼容 四、兼容不同语言 五、兼容不同分辨率 六、兼容不同屏幕方向布局 七、兼容不同硬件 Feature 八、兼容不同SDK平台 一、设备兼容性分类 Android设计用于运行在许多不同…...
React 中的常见 API 和生命周期函数
目录 useStateuseEffectuseRefdangerouslySetInnerHTML生命周期函数 constructorcomponentDidMountstatic getDerivedStateFromPropsshouldComponentUpdatecomponentDidUpdatecomponentWillUnmount useState useState 是 React 的一个 Hook,用于在函数组件中添加…...
神经网络中遇到的 python 函数(Pytorch)
1.getattr() 函数用于返回一个对象属性值。 def getattr(object, name, defaultNone): # known special case of getattr"""getattr(object, name[, default]) -> valueGet a named attribute from an object; getattr(x, y) is equivalent to x.y.When a …...

分布式事务及解决方案
1、分布式事务 分布式事务就是在一个交易中各个服务之间的相互调用必须要同时成功或者同时失败,保持一致性和可靠性。在单体项目架构中,在多数据源的情况下也会发生 分布式事务问题。本质上来说,分布式事务就是为了保证不同数据库的数据一致性…...
【宏定义】——编译时校验
文章目录 编译时校验功能描述代码实现示例代码正常编译示例编译错误示例预处理之后的结果 代码解析!!estruct {int:-!!(e); }sizeof(struct {int:-!!(e); }) 参考代码 编译时校验 功能描述 用于在编译时检查一个条件是否为真,如果条件为真则会编译失败,…...

C#学习系列之System.Windows.Data Error: 40报错
C#学习系列之System.Windows.Data Error: 40报错 前言报错内容解决总结 前言 在用户界面使用上,代码运行没有问题,但是后台报错,仔细研究了报错内容,解决问题,所以记录一下。 报错内容 System.Windows.Data Error: 4…...

【java安全】RMI
文章目录 【java安全】RMI前言RMI的组成RMI实现Server0x01 编写一个远程接口0x02 实现该远程接口0x03 Registry注册远程对象 Client 小疑问RMI攻击 【java安全】RMI 前言 RMI全称为:Remote Method Invocation 远程方法调用,是java独立的一种机制。 RM…...

rcu链表综合实践
基础知识 rcu-read copy update的缩写。和读写锁起到相同的效果。据说牛逼一点。对于我们普通程序员,要先学会使用,再探究其内部原理。 链表的数据结构: struct list_head {struct list_head *next, *prev; };还有一种:struct h…...
odoo16-python框架-动作
总结 1 模型和视图的 设计之美 view_ids, view_id,view_mode 最终目的都是为了生成views, 也就是视图. 模型是死的,像男人,一成不变 视图像女人,千变万化, 姿态万千 一阴一阳之谓道,设计之美又在这里得到了体现 2 所有的动作都可以通过web界面来配置 可以通过在"设…...

微信小程序——同一控件的点击与长按事件共存的解决方案
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...