C++【组合模式】

简单介绍

组合模式是一种结构型设计模式, 只有在可以将对象拆分为【树状结构】的情况下使用。并且像使用独立对象一样使用它们。
常用于表示与图形打交道的用户界面组件或代码的层次结构。

基础理解

订单中可能包括各种产品, 这些产品放置在盒子中, 然后又被放入一层又一层更大的盒子中。 整个结构看上去像是一棵倒过来的树。

Q:为什么要用组合模式 ?
A:在一个树状的结构中,叶子节点与父节点的距离会很远,使用循环语句一个一个遍历会大大增加复杂度。所以需要使用一个通用接口将叶子节点与父节点实现交互。

Q:如何设计组合模式 ?
A:对于一个字节点,直接返回你需要的东西,对于父节点,则会遍历其所有的子节点,最后 " 汇总 " 结果给其父节点
组合模式以递归方式处理对象树中的所有项目


优点:无需了解构成树状结构的对象的具体类,无论复杂简单。直接调用接口进行处理,对象会沿着树结构递归下去

识别方法: 组合模式会将同一抽象或接口类型的实例放入树状结构。

UML

在这里插入图片描述

实现方式

  1. 首先确保你自己 的模型能被拆分为树状结构。并且尝试分解为简单元素和容器(必须能够同时包含简单元素和其他容器)
  2. 声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。
  3. 创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。
  4. 创建一个容器类表示复杂元素,创建一个数组成员变量来存储对于其子元素的引用。需要能同时保存叶节点和容器(父类)
  5. 在容器中定义添加和删除子元素的方法。

#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;
    }
    /**
     * 基础组件可以实现一些默认行为,或者将其委托给具体类(通过将包含行为的方法声明为“abstract”)。
     */
    virtual std::string Operation() const = 0;
};

/**
 * 叶级类表示组合的末端对象。叶级对象不能有任何子节点。
 *
 * 通常情况下,真正的工作是由叶级对象完成的,而复合对象只是将任务委派给它们的子组件。
 */
class Leaf : public Component
{
public:
    std::string Operation() const override
    {
        return "Leaf";
    }
};

/**
 * Composite 类代表可能具有子组件的复杂组件。
 * 通常,Composite 对象将实际工作委托给它们的子组件,
 * 然后“汇总”结果。
 */
class Composite : public Component
{
protected:
    std::list<Component *> children_;

public:
    /**
     * Composite 对象可以向其子组件列表中添加或删除其他组件(简单或复杂)。
     */
    void Add(Component *component) override
    {
        this->children_.push_back(component);
        component->SetParent(this);
    }

    /**
     * 注意,此方法只是从列表中移除指针,但不释放内存,
     * 您应该手动释放内存,或者最好使用智能指针。      
     * 我使用手动,简单理解
     */
    void Remove(Component *component) override
    {
        children_.remove(component);
        component->SetParent(nullptr);
        delete component;
    }

    bool IsComposite() const override
    {
        return true;
    }

    /**
     * Composite 在特定方式下执行其主要逻辑。
     * 它递归遍历所有子组件,收集和汇总它们的结果。
     * 由于 Composite 的子组件会将这些调用传递给它们的子组件,以此类推,
     * 整个对象树将被遍历并返回结果。
     */
    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(); //同一接口
    // ...
}

/**
 * 由于子组件管理操作在基类 Component 中声明,
 * 客户端代码可以处理任何组件,无论是简单还是复杂,而不依赖于具体的类。
 */
void ClientCode2(Component *component1, Component *component2)
{
    // ...
    if (component1->IsComposite())
    {
        component1->Add(component2);
    }
    std::cout << "RESULT: " << component1->Operation();
    // ...
}

/**
 * 这样,客户端代码可以支持简单的叶子组件...
 */

int main()
{
    Leaf *leaf = new Leaf;
    std::cout << "Client: 我有一个简单的组件:\n";
    ClientCode(leaf);
    std::cout << "\n\n";

    /**
     * ...以及复杂的 Composite 组件。
     */
    Composite *tree = new Composite;
    Composite *branch1 = new Composite;
    Composite *branch2 = new Composite;
    Leaf *leaf1 = new Leaf;
    Leaf *leaf2 = new Leaf;
    Leaf *leaf3 = new Leaf;
    Leaf *leaf4 = new Leaf;

    branch1->Add(leaf1);
    branch1->Add(leaf2);
    branch2->Add(leaf3);
    branch2->Add(leaf4);
    tree->Add(branch1);
    tree->Add(branch2);
    std::cout << "Client: 现在我有一个复合树结构:\n";
    ClientCode(tree);
    std::cout << "\n\n";

    std::cout << "Client: 即使在管理树结构时,我不需要检查组件的具体类:\n";
    ClientCode2(tree, leaf);
    std::cout << "\n";

    delete leaf;
    delete tree;
    delete branch1;
    delete branch2;
    delete leaf1;
    delete leaf2;
    delete leaf3;
    delete leaf4;
    system("pause");
    return 0;
}

应用场景

  1. 实现树状对象结构, 可以使用组合模式。

大致可以理解为,两种共享公共接口的基本元素: 简单叶节点和复杂容器(包含其他容器)

  1. 客户端代码以相同方式处理简单和复杂元素

同一接口就是相同方式,但可以使用组合模式同时处理简单和复杂元素。

与其他模式的关系

  1. 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  2. 你可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。

  3. 责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  4. 你可以使用迭代器模式来遍历组合树。

  5. 你可以使用访问者模式对整个组合树执行操作。

  6. 你可以使用享元模式实现组合树的共享叶节点以节省内存。

  7. 组合和装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

  8. 装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。

  9. 但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。

  10. 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每天学习一个Linux命令之curl

每天学习一个Linux命令之curl 在Linux系统中&#xff0c;有很多有用的命令可以帮助我们与网络进行交互。一个非常常用的命令是curl&#xff0c;它是一个功能强大的工具&#xff0c;可用于发送、接收和处理各种网络请求。本文将详细介绍在Linux下使用curl命令的各种选项及其用法…

011_C标准库函数之<time.h>

头文件<time.h>中说明了一些用于处理日期和时间的类型和函数。其中的一部分函数用于处理当地时间&#xff0c;因为时区等原因&#xff0c;当地时间与日历时间可能不相同。clock_t和time_t是两个用于表示时间的算术类型&#xff0c;而struct tm则用于存放日历时间的各个成…

QT学习day1

#include "mywidget.h"myWidget::myWidget(QWidget *parent): QWidget(parent) {this->resize(645,455);//设置窗口大小this->setWindowTitle("QQ");//设置窗口标题this->setWindowIcon(QIcon("D:\\QQ\\1579398717\\FileRecv\\pictrue\\qq.p…

Linux——线程互斥与互斥锁的使用

目录 前言 一、进程线程间的互斥相关背景概念 二、互斥量&#xff08;互斥锁&#xff09; 三、互斥锁的使用 1.互斥锁的初始化 2.加锁与解锁 3.锁的使用 4.锁的封装 四、线程饥饿 五、互斥锁的原理 六、死锁 前言 我们学习过线程概念与线程控制&#xff0c;知道了线…

Django项目定时任务django-crontab

首先定义一个定时任务函数tasks.py&#xff08;见文章末尾示例&#xff09;&#xff0c;编写函数&#xff0c;然后在setting.py中配置定时任务 1、首先安装django-crontab pip install django-crontab 2、在setting.py中添加应用 (在所有自定义注册app之上) INSTALLED_APPS …

第四百四十六回

文章目录 1. 概念介绍2. 使用方法3. 示例代码4. 经验与总结4.1 经验分享4.2 内容总结 我们在上一章回中介绍了"overlay_tooltip简介"相关的内容&#xff0c;本章回中将再谈flutter_launcher_icons包.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我…

Advanced RAG 02:揭开 PDF 文档解析的神秘面纱

编者按&#xff1a; 自 2023 年以来&#xff0c;RAG 已成为基于 LLM 的人工智能系统中应用最为广泛的架构之一。由于诸多产品的关键功能&#xff08;如&#xff1a;领域智能问答、知识库构建等&#xff09;严重依赖RAG&#xff0c;优化其性能、提高检索效率和准确性迫在眉睫&am…

Android详细介绍POI进行Word操作(小白可进)

poi-tl是一个基于Apache POI的Word模板引擎&#xff0c;也是一个免费开源的Java类库&#xff0c;你可以非常方便的加入到你的项目中&#xff0c;并且拥有着让人喜悦的特性。 一、使用poi前准备 1.导入依赖&#xff1a; 亲手测过下面Android导入POI依赖的方法可用 放入这个 …

如何成为一名优秀的工程师下

身为工程师&#xff0c;理所当然要重视实践&#xff0c;自然科学不管发展到何时都离不开实验。 电子学本身就是 为了指导工程实践。所以不要谈空洞的理论。现在很多毕业生都面临这样的问题&#xff0c;总是谈一些空洞的理论&#xff0c;甚至错误的但还不以为然的理论。实践可以…

anylabeling使用和安装

源码地址&#xff1a; git clone https://github.com/vietanhdev/anylabeling.git Auto Labeling with Segment Anything Youtube Demo: https://www.youtube.com/watch?v5qVJiYNX5KkDocumentation: https://anylabeling.nrl.ai Features: Image annotation for polygon, r…

个性化硬盘显示图标的教程

前言 前段时间有个小伙伴说想要个性化硬盘显示的图标&#xff0c;让电脑看起来更加高级。 这看起来确实很高级&#xff0c;这种图标是怎么设置的呢&#xff1f;今天小白就来唠唠&#xff5e; 更换图标的步骤其实并不难&#xff0c;主要的步骤为&#xff1a; 找一个好看的图标…

算法设计与分析(实验5)-----图论—桥问题

一&#xff0e;实验目的 掌握图的连通性。掌握并查集的基本原理和应用。 二&#xff0e;实验步骤与结果 1.定义 &#xff08;1&#xff09;图的相关定义 图&#xff1a;由顶点的有穷非空集合和顶点之间的边的集合组成。 连通图&#xff1a;在无向图G中&#xff0c;若对于…

idea链接gitlab的token到期

报错 HTTP Request Request GET http://36.46.143.158:6060/api/v4/version failed wit

全坚固笔记本丨工业笔记本丨三防笔记本相较于普通笔记本有哪些优势?

三防笔记本和普通笔记本在设计和性能方面存在显著差异&#xff0c;三防笔记本相较于普通笔记本具备以下优势&#xff1a; 三防笔记本通常采用耐磨、耐摔的材料&#xff0c;并具有坚固的外壳设计&#xff0c;能够承受恶劣环境和意外碰撞&#xff0c;有效保护内部组件不受损坏。相…

Flutter第六弹 基础列表ListView

目标&#xff1a; 1&#xff09;Flutter有哪些常用的列表组建 2&#xff09;怎么定制列表项Item&#xff1f; 一、ListView简介 使用标准的 ListView 构造方法非常适合只有少量数据的列表。我们还将使用内置的 ListTile widget 来给我们的条目提供可视化结构。ListView支持…

10倍提效!用ChatGPT编写系统功能文档。。。

系统功能文档是一种描述软件系统功能和操作方式的文档。它让开发团队、测试人员、项目管理者、客户和最终用户对系统行为有清晰、全面的了解。 通过ChatGPT&#xff0c;我们能让编写系统功能文档的效率提升10倍以上。 ​《Leetcode算法刷题宝典》一位阿里P8大佬总结的刷题笔记…

Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks

原文链接&#xff1a;https://arxiv.org/abs/1908.10084 提出契机&#xff1a; 提升相似文本的检索速度 在自然语言处理领域&#xff0c;BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;和RoBERTa&#xff08;A Robustly Optimized B…

mysql修改密码提示: Your password does not satisfy the current policy requirements

1、问题概述&#xff1f; 环境说明&#xff1a; Red Hat Enterprise Linux7mysql5.7.10 执行如下语句报错&#xff1a; set password for rootlocalhost password(123456); ERROR 1819 (HY000): Your password does not satisfy the current policy requirements意思就是&a…

深度学习之使用BP神经网络识别MNIST数据集

目录 补充知识点 torch.nn.LogSoftmax() torchvision.transforms transforms.Compose transforms.ToTensor transforms.Normalize(mean, std) torchvision.datasets MNIST&#xff08;手写数字数据集&#xff09; torch.utils.data.DataLoader torch.nn.NLLLoss() to…

Vue 有哪些主要的指令修饰符

目录 1. 什么是指令修饰符 2. 指令修饰符有哪些 2.1. 按键修饰符 2.2. v-model修饰符 2.3. 事件修饰符 1. 什么是指令修饰符 通过 "." 指明一些指令 后缀&#xff0c;不同 后缀 封装了不同的处理操作 目的&#xff1a;简化代码 2. 指令修饰符有哪些 2.1. 按键…