组合模式(Composite)

news2024/9/30 3:32:00

别名

对象树(Object Tree)。

定义

组合是一种结构型设计模式,你可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用它们

前言

1. 问题

如果应用的核心模型能用树状结构表示,在应用中使用组合模式才有价值。

例如你有两类对象:产品和盒子。一个盒子中可以包含多个产品或者几个较小的盒子。这些小盒子中同样可以包含一些产品或者更小的盒子。

假设你希望在这些类的基础上开发一个订购系统。订单中可以包含无包装的简单产品,也可以包含装满产品的盒子。此时你会如何计算每张订单的总价格呢?

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

2. 解决方案

组合模式建议使用一个通用结构来与「产品」和「盒子」进行交互,并且在该接口中声明一个计算总价的方法:

  • 对于一个具体产品,该方法直接返回其价格
  • 对于一个盒子,该方法遍历盒子中的所有项目,返回该盒子的总价格

该方法的最大优点在于你无需了解构成树状结构的对象的具体类。你也无需了解对象是简单的产品还是复杂的盒子。你只需要调用通用接口以相同的方式对其进行处理即可。当你调用该方法后,对象会将请求沿着树结构传递下去。

结构

  1. 组件(Component)接口描述了树中简单项目和复杂项目所共有的操作。
  2. 叶节点(Leaf)是树的基本结构,它不包含子项目。一般情况下,叶节点最终会完成大部分的实际工作,因为它们无法将工作指派给其他部分。
  3. 容器(Container)——又名“组合(Composite)”——是包含叶节点或其他容器等子项目的单位。容器不知道其子项目所属的具体类,它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目,处理中间结果,然后将最终结果返回给客户端。
  4. 客户端(Client)通过组件接口与所有项目交互。因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。

适用场景

  • 如果你需要实现树状对象结构,可以使用组合模式。

组合模式为你提供了两种共享公共接口的基本元素类型:简单叶节点和复杂容器。容器中可以包含叶节点和其他容器。这使得你可以构建树状嵌套递归对象结构。

  • 如果你希望客户端代码以相同方式处理简单和复杂元素,可 以使用该模式。

组合模式中定义的所有元素共用同一个接口。在这一接口的帮助下,客户端不必在意其所使用的对象的具体类。

实现方式

  1. 确保应用的核心模型能够以树状结构表示。尝试将其分解为简单元素和容器。记住,容器必须能够同时包含简单元素和其他容器。
  2. 声明组件接口及其一系列方法,这些方法对简单和复杂元素都有意义。
  3. 创建一个叶节点类表示简单元素。程序中可以有多个不同的叶节点类。
  4. 创建一个容器类表示复杂元素。在该类中,创建一个数组成员变量来存储对于其子元素的引用。该数组必须能够同时保存叶节点和容器,因此请确保将其声明为组合接口类型。实现组件接口方法时,记住容器应该将大部分工作交给其子元素来完成。
  5. 最后,在容器中定义添加和删除子元素的方法。记住,这些操作可在组件接口中声明。这将会违反「接口隔离原则」,因为叶节点类中的这些方法为空。但是,这可以让客户端无差别地访问所有元素,即使是组成树状结构的元素。

优点

  • 你可以利用多态和递归机制更方便地使用复杂树结构。
  • 开闭原则。无需更改现有代码,你就可以在应用中添加新元素,使其成为对象树的一部分。

缺点

对于功能差异较大的类,提供公共接口或许会有困难。在特定情况下,你需要过度一般化组件接口,使其变得令人难以理解。

Component.hpp

#ifndef B842BDC6_FD59_41BB_ADCB_14F61135BC67
#define B842BDC6_FD59_41BB_ADCB_14F61135BC67
class Graphic{
    public:
        virtual void move2somewhere(int x, int y) = 0;
        virtual void draw() = 0;
};

#endif /* B842BDC6_FD59_41BB_ADCB_14F61135BC67 */

Composite.hpp

#ifndef C9FCC670_FA18_4EA5_AEB1_3A11E300F917
#define C9FCC670_FA18_4EA5_AEB1_3A11E300F917

#include <map>
#include "Component.hpp"

// 组合类表示可能包含子项目的复杂组件。组合对象通常会将实际工作委派给子项目,然后“汇总”结果。
class CompoundGraphic : public Graphic {
 public:
    void add(int id, Graphic* child) {
        childred_[id] = (child);
    }
    void remove(int id) {
        childred_.erase(id);
    }
    void move2somewhere(int x, int y) override {
        for (auto iter = childred_.cbegin(); iter != childred_.cend(); iter++) {
            iter->second->move2somewhere(x, y);
        }
    }
    void draw() override {
        for (auto iter = childred_.cbegin(); iter != childred_.cend(); iter++) {
            iter->second->draw();
        }
    }

 private:
    // key是图表id, value是图表指针
    std::map<int, Graphic*> childred_;
};

#endif /* C9FCC670_FA18_4EA5_AEB1_3A11E300F917 */

Leaf.hpp

#ifndef D829536F_1260_46D0_8D1A_F9809F1BCD83
#define D829536F_1260_46D0_8D1A_F9809F1BCD83
#include <cstdio>
#include "Component.hpp"
class Dot : public Graphic {
 public:
    Dot(int x, int y) : x_(x), y_(y) {}
    void move2somewhere(int x, int y) override {
        x_ += x;
        y_ += y;
        return;
    }
    void draw() override {
        printf("在(%d,%d)处绘制点\n", x_, y_);
        return;
    }

 private:
    int x_;
    int y_;
};

// 圆
class Circle : public Graphic {
 public:
    explicit Circle(int r, int x, int y) : radius_(r), x_(x), y_(y) {}
    void move2somewhere(int x, int y) override {
        x_ += x;
        y_ += y;
        return;
    }
    void draw() override {
        printf("以(%d,%d)为圆心绘制半径为%d的圆\n", x_, y_, radius_);
    }

 private:
    // 半径与圆心坐标
    int radius_;
    int x_;
    int y_;
};

#endif /* D829536F_1260_46D0_8D1A_F9809F1BCD83 */

main.cpp

#include "Composite.hpp"
#include "Leaf.hpp"
int main() {
    // 组合图
    CompoundGraphic* all = new CompoundGraphic();

    // 添加子图
    Dot* dot1 = new Dot(1, 2);
    Circle *circle = new Circle(5, 2, 2);
    CompoundGraphic* child_graph = new CompoundGraphic();
    Dot* dot2 = new Dot(4, 7);
    Dot* dot3 = new Dot(3, 2);
    child_graph->add(0, dot2);
    child_graph->add(1, dot3);

    // 将所有图添加到组合图中
    all->add(0, dot1);
    all->add(1, circle);
    all->add(2, child_graph);

    // 绘制
    all->draw();

    delete all;
    delete dot1;
    delete dot2;
    delete dot3;
    delete circle;
    return 0;
}

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

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

相关文章

彭博:为完善Vision Pro体验,苹果扩招数千名新员工

彭博社记者Mark Gurman在最新一期Power On栏目中表示&#xff0c;苹果在WWDC 2023上公布Vision Pro头显&#xff0c;只是该公司进入XR市场的第一步&#xff0c;实际上该设备在明年才会推出完整版。而且据项目相关人士透露&#xff0c;Vision Pro的软件生态还需要很长时间发展。…

软件工程——第6章详细设计知识点整理

本专栏是博主个人笔记&#xff0c;主要目的是利用碎片化的时间来记忆软工知识点&#xff0c;特此声明&#xff01; 文章目录 1.详细设计阶段的根本目的是&#xff1f; 2.详细设计的任务&#xff1f; 3.详细设计的结果地位&#xff1f;如何衡量程序质量&#xff1f; 4.结构程…

在GitHub上爆火!跳槽必看《Java 面试突击核心讲》知识点笔记整理

不知道大家在面试中有没有这种感觉&#xff1a;面试官通常会在短短两小时内对面试者的知识结构进行全面了解&#xff0c;面试者在回答问题时如果拖泥带水且不能直击问题的本质&#xff0c;则很难充分表现自己&#xff0c;最终影响面试结果。 所以针对这种情况&#xff0c;这份…

从0到1精通自动化测试,pytest自动化测试框架,使用自定义标记mark(十一)

一、前言 pytest可以支持自定义标记&#xff0c;自定义标记可以把一个web项目划分多个模块&#xff0c;然后指定模块名称执行 app自动化的时候&#xff0c;如果想android和ios公用一套代码时&#xff0c;也可以使用标记功能&#xff0c;标明哪些是ios用例&#xff0c;哪些是a…

除静电设备给我们的生产带来怎样的便利

一般来说&#xff0c;我们需要根据具体的生产工艺和场景选择适当的静电设备&#xff0c;并按照厂商提供的操作规范正确使用&#xff0c;以确保除静电设备有效发挥作用。 1. 静电消除&#xff1a;静电设备可以帮助消除物体表面的静电电荷&#xff0c;防止静电积聚。静电积聚可能…

UML类图设计

1.普通类&#xff0c;抽象类&#xff0c;接口 普通类 抽象类 接口 1 关联关系 依赖关系 关联&#xff1a;对象之间的引用关系 依赖&#xff1a;耦合性最低&#xff0c;一些静态方法等 2 聚合关系 组合关系 聚合&#xff1a;整体与部分的关系&#xff0c;但是部分可以脱…

英特尔 oneAPI 2023 黑客松大赛:赛道二机器学习:预测淡水质量 实践分享

目录 一、问题描述二、解决方案1、方案简述2、数据分析预处理特征类型处理特征分布分析 3、特征构造4、特征选择过滤法重要性排序 5、模型训练 总结未来工作 一、问题描述 淡水是我们最重要和最稀缺的自然资源之一&#xff0c;仅占地球总水量的 3%。它几乎触及我们日常生活的方…

Python:pyecharts可视化

文章目录 简介Geo地理图绘制折线图区域突出显示横坐标带选择展示 add地图Mapformatter控制value显示在图中显示value值目标html的解析自定义地图js资源原生地图js的解析解决省份上文字不居中的问题 桑基图设置桑基柱的颜色 参考文献 简介 &#xff08;这是20年的笔记&#xff…

医疗陪诊小程序开发功能有哪些?

医疗陪诊系统开发功能有哪些&#xff1f; 1、注册登录。用户初次使用需使用个人手机号码或者是第三方社交账号进行注册登录&#xff0c;登陆之后填写个人相关信息&#xff0c;姓名、性别、年龄、过往病史、病历等信息&#xff0c;以便医生可以根据患者资料进行初步判断。…

小文智能自定义变量详解

在小文交互场景设计时&#xff0c;有一个特殊功能&#xff0c;叫做自定义变量。有时&#xff0c;根据外呼对象的不同&#xff0c;需要对用户传达不同的内容&#xff0c;比如称呼、地址、公司名称等等。此时&#xff0c;就可以使用小文交互的自定义变量功能来实现对不同用户呼出…

Destination unreachable(Port unreachable) 错误原因和解决办法

Destination unreachable(Port unreachable) 是一条由网络设备&#xff08;如路由器或防火墙&#xff09;生成的ICMP&#xff08;Internet Control Message Protocol&#xff09;错误消息&#xff0c;用于通知源设备目标设备或端口无法到达。 一、什么是ICMP ICMP&#xff08;I…

【中危】Guava<32.0.0 存在竞争条件漏洞

漏洞描述 Guava 是 Google 公司开发的开源 Java 代码库&#xff0c;提供常用的Java工具和数据结构。 Guava 1.0 至 31.1 版本中的 FileBackedOutputStream 类使用Java的默认临时目录创建文件&#xff0c;由于创建的文件名容易被攻击者猜测&#xff0c;在 Unix 和 Android Ice…

静电设备在静电处理环节中的原理

静电设备在静电处理环节中发挥着重要的作用。以下是一些常见的静电设备及其作用&#xff1a; 1. 静电消除器&#xff1a;静电消除器通过释放相等数量的正负离子&#xff0c;有效地中和周围环境中的静电荷&#xff0c;从而减少或消除静电引起的问题&#xff0c;例如静电吸附、电…

AI科技的应用革命:改变生活方式、提升人类生产力

人工智能技术的发展和应用&#xff0c;正在对我们的生活方式产生深远的影响。无论是在家庭、工作还是娱乐方面&#xff0c;越来越多的AI工具正在改变我们的习惯、观念和行为。它们为我们提供了更加智能化、个性化和定制化的服务和产品&#xff0c;让我们的生活变得更加便捷、高…

NXP i.MX 8M Plus工业核心板硬件说明书( 四核ARM Cortex-A53 + 单核ARM Cortex-M7,主频1.6GHz)

1 硬件资源 创龙科技SOM-TLIMX8MP是一款基于NXP i.MX 8M Plus的四核ARM Cortex-A53 单核ARM Cortex-M7异构多核处理器设计的高端工业核心板&#xff0c;ARM Cortex-A53(64-bit)主处理单元主频高达1.6GHz&#xff0c;ARM Cortex-M7实时处理单元主频高达800MHz。处理器…

【教程】Flutter与Rust完美交互,无需手写FFI代码

实践环境&#xff1a;Windows11 flutter_rust_bridge官方文档 Flutter环境配置教程 | Rust环境配置教程 新建一个全新的Flutter项目并运行&#xff1a; flutter create example && cd example && flutter run 在Flutter项目根目录新建一个Rust项目&#xf…

从0到1精通自动化,接口自动化测试——数据驱动DDT实战

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 DDT简介 名称&am…

FPGA解码 MIPI 视频 OV4689采集 4line 2.7K分辨率 提供工程源码和技术支持

目录 1、前言2、Xilinx官方主推的MIPI解码方案3、本 MIPI CSI2 模块性能及其优越性4、我这里已有的 MIPI 编解码方案5、vivado工程介绍5、上板调试验证6、福利&#xff1a;工程代码的获取 1、前言 FPGA图像采集领域目前协议最复杂、技术难度最高的应该就是MIPI协议了&#xff…

python spider 爬虫 之 解析 xpath 、jsonpath、BeautifulSoup (三)

BeautifulSoup 简称&#xff1a;bs4 BeautifulSoup跟lxml 一样&#xff0c;是一个html文档的解析器&#xff0c;主要功能也是解析和提取数据 优缺点 缺点&#xff1a;效率没有lxml的效率高 优点&#xff1a;接口接口人性化&#xff0c;使用方便 延用了css选择器 安装Beautifu…

故障处理程序框图原理

一、故障处理程序框图 故障处理程序包括保护软压板的投切检查、保护定值比较、保护逻辑判断、跳闸处理程序和后加速部分。故障处理程序框图如图2&#xff0d;8所示。保护逻辑判断程序将在第三章中详述。 进入故障处理程序入口&#xff0c;首先置标志位KST为1&#xff0c;驱动起…