C++【组合模式】

news2024/11/29 6:31:37

简单介绍

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

基础理解

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

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

    • 装饰类似于组合,对一个产品套上一层层装饰,最终的结构十分相似。 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。
    • 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。
    • 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。

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

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

相关文章

JavaScript - 你知道数组去重都有哪些实现方案吗

难度级别:初级及以上 提问概率:70% 数组去重是一道非常经典而又高频的面试题,这里我们提出6种解决方案: 目录 1 第一种 2 第二种 3 第三种 4 第四种

Tokenize Anything via Prompting

SAM的延续&#xff0c;把SAM输出的token序列用来进行分类&#xff0c;分割和一个自然语言的decoder处理&#xff0c;但其实现在多模态的图像的tokenizer也几乎都是用VIT来实现的。一开始认为这篇文章可能是关于tokenize的&#xff0c;tokenize还是很重要的&#xff0c;后来看完…

MUX VLAN

目录 原理概述 实验目的 实验内容 实验拓扑 1.基本配置 2.使用Hybrid端口实现网络需求 3.使用Mux VLAN实现网络需求 原理概述 在实际的企业网络环境中&#xff0c;往往需要所有的终端用户都能够访问某些特定的服务器&#xff0c;而用户之间的访问控制规则则比较复杂。在…

Mysql启动报错:本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止

Mysql启动报错&#xff1a;本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止 文章目录 Mysql启动报错&#xff1a;本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止1. 备份mysql的data文件夹2. 重新构建 Wind…

helm与k8s

文章目录 一、helm二、K8S/K3S1.K8S基本组件1.1 资源对象1.2 核心组件1.3典型的创建 Pod 的流程1.4 Kubernetes 多组件之间的通信原理 2. YAML 文件2.1 Maps2.2 Lists2.3 使用 YAML 创建 Pod2.4 创建 Deployment 4.静态pod4.1 配置文件4.2 通过 HTTP 创建静态 Pods4.3 静态pods…

【Linux系列】如何确定当前运行的是 RHEL 9 还是 RHEL 8?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

zheng项目:从零到一打造全方位J2EE企业级开发解决方案

zheng项目&#xff1a;从零到一打造全方位J2EE企业级开发解决方案 摘要&#xff1a; 在当今快速发展的企业级应用开发领域&#xff0c;一套高效、稳定且可扩展的解决方案对于企业的成功至关重要。zheng项目旨在提供一套全面的J2EE企业级开发解决方案&#xff0c;从前端模板到自…

学习人工智能:为何PyTorch深度学习框架不可或缺

在人工智能&#xff08;AI&#xff09;的浩瀚领域中&#xff0c;深度学习作为其核心分支&#xff0c;正以其强大的数据处理能力、模式识别能力和预测能力引领着科技的飞速发展。而在深度学习的众多工具与框架中&#xff0c;PyTorch无疑是一颗璀璨的明星。本文将从PyTorch的特点…

单片机为什么还在用C语言编程?

单片机产品的成本是非常敏感的。因此对于单片机开发来说&#xff0c;最重要的是在极其有限的ROM和RAM中实现最多产品的功能。或者反过来说&#xff0c;实现相同的产品功能&#xff0c;所需要的ROM和RAM越小越好&#xff0c;在开始前我有一些资料&#xff0c;是我根据网友给的问…

JMeter+Ant+Jenkins构建接口报告(无人驾驶版)

展示结果&#xff1a; uc浏览器打开测试报告&#xff0c;绿色显示脚本结果 搭建操作步骤如下 1.jemter写好脚本 2.下载并配置ant环境变量&#xff1a;加上activation.jar、commons-lang3-3.8.1.jar、mail.jar 这3个包 mail.jar需要引用到jmeter 3.下载安装Jenkins 并进行构建…

算法 - 符号表-上

&#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 算法 - 符号表前言初级实现1. 链表实现无序符号表2. 二分查找实现有序符号表 二叉查找树1. get()2. put()3. 分析4. floor()5. rank()6. min()7. deleteMin()8. delete()9. keys()10. 分析 算法 - 符号表 前言 符号表&am…

MQTT的学习

近期构建物联网平台&#xff0c;学习到MQTT&#xff0c;这里使用的是uniapp作为连接MQTT broker的&#xff0c;这里使用的是国产的EMQX。 MQTT的认识 MQTT 协议入门&#xff1a;基础知识和快速教程 | EMQ&#xff08;简单的认识&#xff09; 创建 MQTT 连接时如何设置参数&am…

前端开发语言有那些?

前端开发语言有那些&#xff1f; 1、html 超文本标记语言&#xff1a;构建前端网页的基本结构&#xff0c;就象人的骨架一样。 2、css 层叠样式表&#xff1a;控制网页的样式和布局&#xff0c;就象人需要穿各种服式展现不同风采。 3、javascript 简称 JS 动态脚本语言&#x…

微信小程序云函数调用方法和技术架构介绍

云函数 云函数是涂鸦根据微信小程序使用场景&#xff0c;结合涂鸦 IoT 开放能力&#xff0c;提供的小程序访问涂鸦 IoT 开放能力接口方案。为此在基础能力中&#xff0c;我们提供了基础请求云函数的 API。 对于特殊的业务场景&#xff0c;需要使用云函数访问业务数据的&#…

智慧园区预约管理系统:提升效率与保障安全的关键

在当今这个信息技术高度发达的时代&#xff0c;智慧园区如雨后春笋般迅速发展&#xff0c;而预约管理作为智慧园区的关键组成部分&#xff0c;其重要性日益凸显。 访客预约系统的精细化设计&#xff0c;为园区的安全和秩序提供了坚实可靠的保障。访客可以通过便捷的在线平台&am…

django系统模板

【一】引子 来看一段代码 def current_datetime(request):now datetime.datetime.now()html "<html><body>It is now %s.</body></html>" % nowreturn HttpResponse(html)直接把HTML页面嵌套在视图函数里返回给浏览器并不是一个好主意&a…

2024年天津中德应用技术大学退役大学生专升本专业考试准考证下载

2024年天津中德应用技术大学退役大学生高职升本科专业课考试准考证下载及考生须知 一、准考证下载打印 4月7日14点开始&#xff0c;天津中德应用技术大学专业课报名审核通过的考生&#xff0c;登录天津中德应用技术大学专业课报名系统&#xff08;http://125.65.42.21:8091/j…

【实战解析】YOLOv9全流程训练至优化终极指南

【实战解析】YOLOv9全流程训练至优化终极指南 0.引言1.环境准备2.数据预处理&#xff08;1&#xff09;数据准备&#xff08;2&#xff09;按比例划分数据集&#xff08;3&#xff09;xml转txt脚本&#xff08;4&#xff09;配置文件 3.模型训练&#xff08;1&#xff09;单GPU…

4.7Qt

自由发挥应用场景实现一个登录窗口界面。 mywidget.cpp #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//窗口相关设置this->setWindowTitle("原神启动");this->setWindowIcon(QIcon("C:\\Users\\17212\\Pict…

【学习】移动端App性能测试流程有哪些

移动端App性能测试是保证App性能表现的重要环节之一。随着移动设备的普及和移动互联网的发展&#xff0c;移动端App的性能测试变得越来越重要&#xff0c;通过科学合理的性能测试可以发现并解决潜在的性能问题优化App运行效果提高用户体验。性能测试旨在评估App在各种场景下的性…