结构型模式--3.组合模式【草帽大船团】

news2024/11/15 12:24:56

1. 好大一棵树

路飞在德雷斯罗萨打败多弗朗明哥之后,一些被路飞解救的海贼团自愿加入路飞麾下,自此组成了草帽大船团,旗下有7为船长,分别是:

俊美海贼团75人
巴托俱乐部56人
八宝水军1000人
艾迪欧海贼团4人
咚塔塔海贼团200人
巨兵海贼团5人
约塔玛利亚大船团4300人
小弟数量总计5640人。
在这里插入图片描述

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

像草帽大船团这样,能将多个对象组成一个树状结构,用以描述部分—整体的层次关系,使得用户对单个对象和组合对象的使用具有一致性,这样的结构性设计模式叫做组合模式

现实生活中能够和组合模式对应的场景也有很多,下面举例说明:

  1. Linux 的树状目录结构
  2. 国家的行政区划分(省级、地级、县级、乡级)
  3. 解放军编制(军、师、旅、团、营、连、排、班)
  4. 公司的组织结构(树状)

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;
};

草帽大船团中有若干个番队,这个抽象类对应的就是以船为单位的一个团队(一艘船就是一个节点),它内部定义了如下方法:

  1. 设置和获得当前船队的名字
    • 设置名字:构造函数
    • 获得名字:getName()
  2. 设置和得到当前船队节点的父节点
    • 设置父节点:setParent(AbstractTeam* node)
    • 得到父节点:getParent()
  3. 给当前番队添加一个子船队节点:add(AbstractTeam* node)
  4. 跟当前番队删除一个子船队节点:remove(AbstractTeam* node)
  5. 当前番队和敌人战斗:fight()
  6. 显示当前番队的信息: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中的组合关系),在AbstractTeamManagerTeam之间画了两条线:

  • 继承关系:对节点的操作使用的是抽象类中提供的接口,以保证操作的一致性
  • 聚合关系:ManagerTeam类型的节点还可以有子节点,父节点和子节点的之间的关系需要具体问题具体分析
    • 子节点跟随父节点一起销毁,二者就是组合关系(UML中的组合关系)
    • 子节点不跟随父节点一起销毁,二者就是聚合关系
    • 上面的程序中,在父节点的析构函数中没有销毁它管理的子节点,所以在上图中标记的是聚合关系

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

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

相关文章

暴雨信息专场推介会亮相武汉国际创科展

4月14日&#xff0c;暴雨信息专场推介会亮相2024年武汉国际创科展。推介会以“智慧聚变新生”为主题&#xff0c;集中展示推介暴雨信息旗下多项颇具市场优势的智能化产品和解决方案&#xff0c;邀请多位专家分享在数字化转型领域的成果和实践经验&#xff0c;与业界同仁共同探讨…

osg渲染过程

目录 1、渲染最简单代码 2、详解run方法 3、详细过程 4、回调函数 5、Node Visitor 1、渲染最简单代码 2、详解run方法 3、详细过程 3.1 advance()方法 进行帧计数 3.2 eventTraversal() eventTraversal()响应用户操作,eventTraversal()遍历的是事件队列&#xff0c;而…

Java开发从入门到精通(二十):Java的面向对象编程OOP:Collection集合框架

Java大数据开发和安全开发 &#xff08;一&#xff09;Java的集合进阶1.1 集合体系结构1.2 Collection集合1.2.1 Collection集合包含哪些接口和实现类1.2.2 Collection集合特点1.2.3 为啥要先学Collection的常用方法?1.2.4 Collection集合的遍历1.2.4.1 迭代器1.2.4.1.1 迭代器…

基于SpringBoot+Vue实现的医院在线挂号系统(代码+万字文档)

系统介绍 基于SpringBootVue实现的医院在线挂号系统设计了三种角色&#xff0c;分别是管理员、医生、用户&#xff0c;每种角色对应不同的菜单 系统实现了个人信息管理、基础数据管理、论坛管理、用户管理、单页数据管理、医生管理及轮播图管理等功能模块&#xff0c;具体功能…

16.读取指定路径下的txt文档然后合并内容为一个txt文档。

1.题目要求 分别读取路径为 ./middle/phone/base/1_student_0.txt, ./middle/vr/base/1_teacher.txt, ./nearby/phone/base/1_student_0.txt, ./nearby/vr/base/1_teacher.txt, ./outside/phone/base/1_student_0.txt, ./outside/vr/base/1_teacher.txt 里面的文件&#xff…

【LeetCode: 3117. 划分数组得到最小的值之和 + 动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

linux shell脚本编写(2)

Shell: 命令转换器&#xff0c;高级语言转换成二进制语言。是Linux的一个外壳&#xff0c;它包在Lniux内核的外面&#xff0c;用户和内核之间的交互提供了一个接口。 内置命令&#xff1a;在shell内部不需要shell编辑 外置命令&#xff1a;高级语言要用shell转换成二进制语言 …

Numpy数组和列表list的区别

参考&#xff1a;Numpy Array vs List 在Python编程中&#xff0c;列表&#xff08;list&#xff09;和Numpy数组&#xff08;numpy array&#xff09;是两种常见的数据结构&#xff0c;它们都可以用来存储多个元素。但是它们在实际使用中有很大的区别&#xff0c;本文将详细比…

【尚硅谷】Git与GitLab的企业实战 学习笔记

目录 第1章 Git概述 1. 何为版本控制 2. 为什么需要版本控制 3. 版本控制工具 4. Git简史 5. Git工作机制 6. Git和代码托管中心 第2章 Git安装 第3章 Git常用命令 1. 设置用户签名 1.1 基本语法 1.2 案例实操 2. 初始化本地库 2.1 基本语法 2.2 案例实操 3. 查…

RabbbitMQ基本使用及其五种工作模型

初识MQ 同步通讯和异步通讯 什么是同步通讯呢&#xff1f;举个例子&#xff0c;你认识了一个小姐姐&#xff0c;聊的很火热&#xff0c;于是你们慢慢开始打电话&#xff0c;视频聊天&#xff0c;这种方式就成为同步通讯&#xff0c;那什么是一部通讯呢&#xff0c;同样的&…

P1706 全排列问题

原题链接:全排列问题 - 洛谷 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 dfs典题 3. 代码实现 #define _CRT_SECURE_NO_WARNINGS 1 #include<bits/stdc.h> using namespace std; #define ll long long #define endl \n const int N 2…

电脑技巧:Bandicam班迪录屏介绍

目录 一、 软件简介 二、软件功能 2.1 屏幕录制 2.2 游戏录制 2.3 设备录制 2.4实时编辑与截图 2.5 轻量级软件 三、软件用途 3.1 教育培训 3.2 游戏直播与分享 3.3 企业办公 3.4 在线教学与知识分享 四、总结 今天给大家推荐一款非常实用的电脑录屏软件&#xf…

Java实现单点登录(SSO)详解:从理论到实践

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

提高大型语言模型 (LLM) 性能的四种数据清理技术

原文地址&#xff1a;four-data-cleaning-techniques-to-improve-large-language-model-llm-performance 2024 年 4 月 2 日 检索增强生成&#xff08;RAG&#xff09;过程因其增强对大语言模型&#xff08;LLM&#xff09;的理解、为它们提供上下文并帮助防止幻觉的潜力而受…

java-字符串

一、String概述 java.lang.String类代表字符串&#xff0c;java中所有字符串文字都是该类的对象 字符串的内容是不会发生变化的&#xff0c;它的对象在创建之后就不能被更改 二、创建String对象 1、直接赋值 语法&#xff1a; String 变量名内容&#xff1b; 2、使用构造…

Linux 磁盘存储挂载

fdisk -l 查看磁盘信息 mkfs.xfs /dev/vdc 格式化数据盘 mkdir vdev1 创建挂载点 mount /dev/vdc /vdev1 文件系统挂载点挂载 df -h 确认挂载成功 vi /etc/fstab 插入 /dev/vdc /vdev1 xfs defaults 0 0 /dev/vdc /vdev1 xfs defaults 0 0 保存就可以了 卸载实例&#…

韩顺平 | 零基础快速学Python(16) 文件处理

文件 输入与输出 输入&#xff1a;数据从数据源(文件)到程序(内存)&#xff1b; 输出&#xff1a;数据从程序(内存)到数据源(文件)。 #mermaid-svg-06PG6JZq4jJMV1oH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-sv…

SpringBoot源码解析-01

SpringBoot3详细版 一&#xff1a;SpringBoot引入&#xff1a; 1.1.什么是Springboot&#xff1a; SpringBoot 帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用&#xff08;说明&#xff1a;SpringBoot底层是Spring&#xff09; 大多数 SpringBoot 应用只需要编…

洛谷 P9532 [YsOI2023] 前缀和

题目背景 Ysuperman 模板测试的试机题。 小心立秋&#xff0c;小心秋丽。 题目描述 立秋有一个长度为 n 的数组 a&#xff0c;所有数字都是正整数&#xff0c;并且除了其中第一个数字以外其它数字都等于前面所有数字的和。 例如&#xff0c;数组 [1,1,2,4,8,16] 就有可能是…

如何入行产品经理?

转产品经理第一点要先学基础理论知识&#xff0c;学了理论再去实践&#xff0c;转行&#xff0c;跳槽&#xff01; 学理论比较好的就是去报NPDP的系统班&#xff0c;考后也会有面试指导课&#xff0c;跟职场晋升课程&#xff0c;对小白来说非常合适了~&#xff08;可以去哔站找…