结构型模式--3.组合模式【草帽大船团】
1. 好大一棵树
路飞在德雷斯罗萨打败多弗朗明哥之后,一些被路飞解救的海贼团自愿加入路飞麾下,自此组成了草帽大船团,旗下有7为船长,分别是:
俊美海贼团75人
巴托俱乐部56人
八宝水军1000人
艾迪欧海贼团4人
咚塔塔海贼团200人
巨兵海贼团5人
约塔玛利亚大船团4300人
小弟数量总计5640人。

对于草帽大船团的结构组成,很像一棵树:路飞是这棵树的根节点,旗下的七个船长是路飞的子节点。在这七个船长的旗下可能还有若干个船长。。。

像草帽大船团这样,能将多个对象组成一个树状结构,用以描述部分—整体的层次关系,使得用户对单个对象和组合对象的使用具有一致性,这样的结构性设计模式叫做组合模式。
现实生活中能够和组合模式对应的场景也有很多,下面举例说明:
- Linux 的树状目录结构
- 国家的行政区划分(省级、地级、县级、乡级)
- 解放军编制(军、师、旅、团、营、连、排、班)
- 公司的组织结构(树状)
2. 大决战
在海贼中,大家都预测路飞的对手应该是同为四皇的黑胡子,黑胡子手下也有很多海贼船,双方一旦开战,必定死伤无数,最后的赢家就可以得到罗杰所留下的大秘宝ONE PIECE,并成为新的海贼王。
为了让路飞成为海贼王,我决定使用组合模式为路飞写一个管理草帽大船团的程序,其对应的主要操作是这样的:扩充船员、战斗减员、显示各船队信息、加入战斗等。
2.1 团队管理
对于组合模式来说,操作这个集合中的任意一个节点的方式都是相同的,所以必须要先定义出单个节点的抽象,在这个抽象类中定义出节点的行为。
// 抽象节点
class AbstractTeam
{
public:AbstractTeam(string name) :m_name(name) {}// 设置父节点void setParent(AbstractTeam* node){m_parent = node;}AbstractTeam* getParent(){return m_parent;}string getName(){return m_name;}virtual bool hasChild(){return false;}virtual void add(AbstractTeam* node) {}virtual void remove(AbstractTeam* node) {}virtual void fight() = 0;virtual void display() = 0;virtual ~AbstractTeam() {}
protected:string m_name;AbstractTeam* m_parent = nullptr;
};
草帽大船团中有若干个番队,这个抽象类对应的就是以船为单位的一个团队(一艘船就是一个节点),它内部定义了如下方法:
- 设置和获得当前船队的名字
- 设置名字:构造函数
- 获得名字:
getName()
- 设置和得到当前船队节点的父节点
- 设置父节点:
setParent(AbstractTeam* node) - 得到父节点:
getParent()
- 设置父节点:
- 给当前番队添加一个子船队节点:
add(AbstractTeam* node) - 跟当前番队删除一个子船队节点:
remove(AbstractTeam* node) - 当前番队和敌人战斗:
fight() - 显示当前番队的信息:
display()
2.2 叶子节点
草帽大船团是一种组合模式,也就是一种树状结构,在最末端的节点就没有子节点了,这种节点可以将其称之为叶子节点。叶子节点也是一个船队,所以它肯定是需要继承抽象节点类的。
// 叶子节点的小队
class LeafTeam : public AbstractTeam
{
public:using AbstractTeam::AbstractTeam;void fight() override{cout << m_parent->getName() + m_name + "与黑胡子的船员进行近距离肉搏战..." << endl;}void display() override{cout << "我是" << m_parent->getName() << "下属的" << m_name << endl;}~LeafTeam(){cout << "我是" << m_parent->getName() << "下属的" << m_name << ", 战斗已经结束, 拜拜..." << endl;}
};
叶子节点对应的番队由于没有子节点,所以在其对应的类中就不需要重写父类的add(AbstractTeam* node)和remove(AbstractTeam* node)方法了,这也是基类中为什么不把这两个虚函数指定为纯虚函数的原因。
2.3 管理者节点
所谓的管理者节点其实就是非叶子节点。这种节点还拥有子节点,它的实现肯定是需要继承抽象节点类的。
// 管理者节点
class ManagerTeam : public AbstractTeam
{
public:using AbstractTeam::AbstractTeam;void fight() override{cout << m_name + "和黑胡子的恶魔果实能力者战斗!!!" << endl;}void add(AbstractTeam* node) override{node->setParent(this);m_children.push_back(node);}void remove(AbstractTeam* node) override{node->setParent(nullptr);m_children.remove(node);}bool hasChild(){return true;}list<AbstractTeam*> getChildren(){return m_children;}void display(){string info = string();for (const auto item : m_children){if (item == m_children.back()){info += item->getName();}else{// 优先级: + > +=info += item->getName() + ", ";}}cout << m_name + "的船队是【" << info << "】" << endl;}~ManagerTeam(){cout << "我是【" << m_name << "】战斗结束, 拜拜..." << endl;}
private:list<AbstractTeam*> m_children;
};
在管理者节点类的内部有一个容器list,容器内存储的就是它的子节点对象:
通过add(AbstractTeam* node)把当前番队的子节点存储到list中
通过remove(AbstractTeam* node)把某一个子节点从当前番队的list中删除
通过display()来遍历这个list容器中的节点
2.4 战斗
最后把测试程序写一下:
// 内存释放
void gameover(AbstractTeam* root)
{if (root == nullptr){return;}if (root && root->hasChild()){ManagerTeam* team = dynamic_cast<ManagerTeam*>(root);list<AbstractTeam*> children = team->getChildren();for (const auto item : children){gameover(item);}}delete root;
}// 和黑胡子战斗
void fighting()
{vector<string> nameList = {"俊美海贼团", "巴托俱乐部", "八宝水军", "艾迪欧海贼团","咚塔塔海贼团", "巨兵海贼团", "约塔玛利亚大船团"};// 根节点ManagerTeam* root = new ManagerTeam("草帽海贼团");for (int i = 0; i < nameList.size(); ++i){ManagerTeam* child = new ManagerTeam(nameList.at(i));root->add(child);if (i == nameList.size() - 1){// 给最后一个番队添加子船队for (int j = 0; j < 9; ++j){LeafTeam* leaf = new LeafTeam("第" + to_string(j + 1) + "番队");child->add(leaf);leaf->fight();leaf->display();}child->fight();child->display();}}root->fight();root->display();cout << "====================================" << endl;gameover(root);
}int main()
{fighting();return 0;
}
输出的结果为:
约塔玛利亚大船团第1番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第1番队
约塔玛利亚大船团第2番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第2番队
约塔玛利亚大船团第3番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第3番队
约塔玛利亚大船团第4番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第4番队
约塔玛利亚大船团第5番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第5番队
约塔玛利亚大船团第6番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第6番队
约塔玛利亚大船团第7番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第7番队
约塔玛利亚大船团第8番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第8番队
约塔玛利亚大船团第9番队与黑胡子的船员进行近距离肉搏战...
我是约塔玛利亚大船团下属的第9番队
约塔玛利亚大船团和黑胡子的恶魔果实能力者战斗!!!
约塔玛利亚大船团的船队是【第1番队, 第2番队, 第3番队, 第4番队, 第5番队, 第6番队, 第7番队, 第8番队, 第9番队】
草帽海贼团和黑胡子的恶魔果实能力者战斗!!!
草帽海贼团的船队是【俊美海贼团, 巴托俱乐部, 八宝水军, 艾迪欧海贼团, 咚塔塔海贼团, 巨兵海贼团, 约塔玛利亚大船团】
====================================
我是【俊美海贼团】战斗结束, 拜拜...
我是【巴托俱乐部】战斗结束, 拜拜...
我是【八宝水军】战斗结束, 拜拜...
我是【艾迪欧海贼团】战斗结束, 拜拜...
我是【咚塔塔海贼团】战斗结束, 拜拜...
我是【巨兵海贼团】战斗结束, 拜拜...
我是约塔玛利亚大船团下属的第1番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第2番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第3番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第4番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第5番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第6番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第7番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第8番队, 战斗已经结束, 拜拜...
我是约塔玛利亚大船团下属的第9番队, 战斗已经结束, 拜拜...
我是【约塔玛利亚大船团】战斗结束, 拜拜...
我是【草帽海贼团】战斗结束, 拜拜...
由于草帽大船团对应的设计模式是组合模式,它对应的是一个树模型,并且每个节点的操作方式都形同,所以在释放节点的时候就可以使用递归了,gameover()函数就是一个递归函数。
3. 结构图
学完了组合模式,根据上面的例子把对应的UML类图画一下(学会之后就得先画类图,再写程序了)

为了能够更加清楚地描述出设计模式中的组合关系(不是UML中的组合关系),在AbstractTeam和ManagerTeam之间画了两条线:
- 继承关系:对节点的操作使用的是抽象类中提供的接口,以保证操作的一致性
- 聚合关系:ManagerTeam类型的节点还可以有子节点,父节点和子节点的之间的关系需要具体问题具体分析
- 子节点跟随父节点一起销毁,二者就是组合关系(UML中的组合关系)
- 子节点不跟随父节点一起销毁,二者就是聚合关系
- 上面的程序中,在父节点的析构函数中没有销毁它管理的子节点,所以在上图中标记的是聚合关系
相关文章:
结构型模式--3.组合模式【草帽大船团】
1. 好大一棵树 路飞在德雷斯罗萨打败多弗朗明哥之后,一些被路飞解救的海贼团自愿加入路飞麾下,自此组成了草帽大船团,旗下有7为船长,分别是: 俊美海贼团75人 巴托俱乐部56人 八宝水军1000人 艾迪欧海贼团4人 咚塔塔海…...
网络基础三——其他周边问题
3.1ARP原理 ARP不是一个单纯的数据链路层的协议,而是一个介于数据链路层和网络层之间的协议; 以广播的形式(主机号填成全1)构建Mac帧,发送ARP请求包,告诉所有在局域网的主机我的IP地址和Mac帧,与目的IP相同的主…...
学习周报:文献阅读+Fluent案例+水力学理论学习
目录 摘要 Abstract 文献阅读:物理信息的神经网络与湍流传质的非封闭机制模型相结合 文献摘要 提出问题 提出方案 实验设置 所需方程介绍 雷诺时均方程(RANS) K-epsilon两方程模型 神经网络框架 DNN部分 损失函数定义 PINN部分…...
Redis(持久化 -- RDB AOF)
持久化 通常我们认为持久化为: 重启进程/重启主机之后, 数据仍然存在不丢失 把数据存储在硬盘上 – 持久 把数据存储在内存中 – 不持久 Redis 持久化 redis 是一个内存数据库, 也就是说本身是不持久的(但是快[效率高]), 于是 Redis 提供了持久化机制 — RDB 和 AOF 二者都是对…...
LDR6328助力Type-C普及,便捷充电,绿色生活更精彩
随着科技的进步和全球统一接口的需求,Type-C接口正日益受到青睐。越来越多的设备正选择采纳这一先进的接口设计,它的普及无疑在改善着我们的日常生活。 在过往,许多小功率设备如小风扇、蓝牙音箱、桌面台灯以及家用加湿器等,都普遍…...
redis主从复制、哨兵模式、集群
文章目录 redis主从复制主从复制的配置**安装Redis**配置主服务器配置从服务器验证主从效果 哨兵模式哨兵的工作机制哨兵模式的搭建启动哨兵 集群分布式集群的搭建 redis主从复制 Redis主从复制(Redis replication)是Redis提供的一种数据备份和故障转移…...
shell免登陆脚本
#!/bin/bash ## 脚本接收的参数,也就是要互相配置 SSH 免密登录的服务器列表参数 BASE_HOST_LISTip ## 密码,默认用户是当前运行脚本的用户,比如 root 用户 ## 这里改成你的用户对应的密码 BASE_PASSWORD"password" ## shell 函…...
基于springboot+vue+Mysql的职称评审管理系统
开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…...
GitLab教程(一):安装Git、配置SSH公钥
文章目录 序一、Git安装与基本配置(Windows)下载卸载安装基本配置 二、SSH密钥配置 序 为什么要使用代码版本管理工具: 最近笔者确实因为未使用代码版本管理工具遇到了一些愚蠢的问题,笔者因此认为代码版本管理工具对于提高团队…...
【算法】无序数组的两数之和 - map标记
题目 在一个无序数组中找到两个数,两个数之和为给定的一个数,返回两个数在数组中的下标。 原理 遍历数组,遍历到一个数字的时候,记录下这个数及其下标,遍历时判断给定数减去这个数为key在map中是否存在,…...
Prime (2021): 2
前言 这个靶机有亿点难,收获很多。打靶的时候,前面很顺,到创建ssh公钥之后就一点不会了。 1 01 arp扫描,发现有一个130,再查看端口 有22,80,129,445,10123 dirb扫描目录 这…...
React 状态管理:安全高效地修改对象属性的 3 种方法
在 React 应用程序中,状态(state)是驱动整个应用程序的核心。当应用程序的状态发生变化时,React 会自动重新渲染相应的组件,以确保用户界面的更新。 与数组状态一样,对象状态在 React 中也需要特别处理。直接修改对象属性是不被允许的,因为 React 的不可变性原则要求我们创建一…...
python实现pdf的页面替换
利用第三方库PyPDF2,下面例子中进行的是将 origin.pdf 的第17页替换为 s17.pdf 的第1页: import PyPDF2def replace_pages(original_pdf_path, replacement_pages):with open(original_pdf_path, rb) as original_file:original_pdf PyPDF2.PdfReader(…...
[AIGC] Java List和Map常用API以及其Python实现方式对照介绍
Java和Python作为当今非常浅显易懂的编程语言,其数据结构中对于List和Map(Java)或List和Dict(Python)的操作无疑是每个程序员都非常必需的知识。本文将介绍在Java中对List和Map常用的一些操作,并给出在Pyth…...
零基础如何闯入IT的神秘大门?
前言 随着信息技术的飞速发展,IT行业成为了许多有志之士梦寐以求的职业领域。但对于零基础的人来说,如何成功进入这个行业却是一个不小的挑战。下面,我将结合自身的C语言专业知识,为大家详细阐述一条可行的学习路径,并…...
java程序 .exe启动nginx防止重复启动,已解决
java代码生成好的.exe启动nginx服务程序 根据nginx占用端口来解决nginx服务重复启动问题(下面代码了解代码逻辑后根据自己的业务需求修改即可) 代码: package org.example;import javax.swing.*; import java.awt.*; import java.io.*; …...
二十一、Rust 反射 获取类型
不同于 java 中的反射,Rust 没有提供以往意义上的运行时反射,取而代之的是 “编译期反射”,如 类型分析、类型转换、类型签名。但即便如此,也已经能对 Rust元编程 提供很多助力了。 这种操作,主要通过 Any 来实现&…...
Flutter Engine引擎概念
1.Flutter是Google提供的开源框架。 2.本身由C编写并兼容iOS(底层C)/Android(底层C)平台的FlutterEngine框架负责UI渲染、数据转移、调用DartVM虚拟机。 3.FlutterEngine框架由Skia图形库、Dart运行时、Flutter框架代码组成。Skia是用于图形绘制和文本显示的2D图形引擎库&#…...
【运行环境】加载资源的形式
相关资源:性能优化原则 1 加载资源的形式 html代码 媒体文件,如图片,视频等 javasccript css 2 加载资源的过程 DNS解析:域名-> ip地址 浏览器根据IP地址向服务器发送http 请求 服务器处理http 请求,并返回给浏览器…...
备战蓝桥杯Day40 - 第11届python组真题 - C跑步锻炼
一、题目描述 二、思路 1、使用datetime库中的方法可以很好的解决这个问题。 2、定义起始时间和结束时间,判断是否是周一或者是1号,结果res加上相应的里程数。 3、最后输出 res 即为本题答案。 三、代码实现 import datetimestart datetime.date(2…...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
