C++设计模式_20_Composite 组合模式

news2025/3/12 12:33:23

Composite 组合模式和后面谈到的Iterator,Chain of Resposibility都属于“数据结构”模式。Composite 组合模式核心是通过多态的递归调用解耦内部和外部的依赖关系。

文章目录

  • 1. “数据结构”模式
    • 1.1 典型模式
  • 2. 动机( Motivation )
  • 3. 模式定义
  • 4. Composite 组合模式代码分析
  • 5. 结构(Structure)
  • 6. 要点总结
  • 7. 其他参考

1. “数据结构”模式

常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案

1.1 典型模式

  • Composite

  • Iterator

  • Chain of Resposibility

2. 动机( Motivation )

  • 软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。

  • 如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器 ?

3. 模式定义

  • 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。

----《设计模式》GoF

这里的一致性指下面的代码在使用时,不论是树节点还是叶子结点,使用方法都是一致的。

    process(root);
    process(leaf2);
    process(treeNode3);

4. Composite 组合模式代码分析

典型树结构的数据处理,访问的时候使用多态的方式将树形结构的访问封装到了树形结构的内部,而不是要把树形结构暴露在外部。

#include <iostream>
#include <list>
#include <string>
#include <algorithm>

using namespace std;

class Component
{
public:
    virtual void process() = 0;
    virtual ~Component(){}
};

//树节点
class Composite : public Component{
    
    string name;
    list<Component*> elements;
public:
    Composite(const string & s) : name(s) {}
    
    void add(Component* element) {
        elements.push_back(element);
    }
    void remove(Component* element){
        elements.remove(element);
    }
    
    void process(){
        
        //1. process current node
        
        
        //2. process leaf nodes
        for (auto &e : elements)
            e->process(); //多态调用
         
    }
};

//叶子节点
class Leaf : public Component{
    string name;
public:
    Leaf(string s) : name(s) {}
            
    void process(){
        //process current node
    }
};


void Invoke(Component & c){
    //...
    c.process();
    //...
}


int main()
{

    Composite root("root");
    Composite treeNode1("treeNode1");
    Composite treeNode2("treeNode2");
    Composite treeNode3("treeNode3");
    Composite treeNode4("treeNode4");
    Leaf leat1("left1");
    Leaf leat2("left2");
    
    root.add(&treeNode1);
    treeNode1.add(&treeNode2);
    treeNode2.add(&leaf1);
    
    root.add(&treeNode3);
    treeNode3.add(&treeNode4);
    treeNode4.add(&leaf2);
    
    process(root);
    process(leaf2);
    process(treeNode3);
  
}

代码分析:

首先定义一个抽象接口

class Component
{
public:
    virtual void process() = 0;
    virtual ~Component(){}
};

定义了一个树形结构

//树节点
class Composite : public Component{
    
    string name;
    list<Component*> elements;
public:
    Composite(const string & s) : name(s) {}
    
    void add(Component* element) {
        elements.push_back(element);
    }
    void remove(Component* element){
        elements.remove(element);
    }
    
    void process(){
        
        //1. process current node
        
        
        //2. process leaf nodes
        for (auto &e : elements)
            e->process(); //多态调用
         
    }
};

list<Component*> elements;构成子节点,子节点类型为Component,树节点和下面的叶子结点都可以加入树节点类型,也就是list中的对象可能是Composite,也可能是Leaf,因此list<Component*> elements;也就表达了树形结构。

add()和remove()是树形结构的操作。

process()方法是对父类的override,有两个步骤的处理,第一个步骤是处理当前节点,第二步是处理叶子结点,叶子结点是用循环,e->process()是虚函数的调用,如果当前是Composite证明调用的是自身,如果是Leaf,就会调用到Leaf的process(),也就不会再循环。这里是一种递归的思想。

这样就完成Component树节点下的所有树节点。

另外定义叶子结点

//叶子节点
class Leaf : public Component{
    string name;
public:
    Leaf(string s) : name(s) {}
            
    void process(){
        //process current node
    }
};

也继承自 Component。

假如有一个客户的处理程序:接受Component作为参数,进去后调用c.process();实现多态调用

void Invoke(Component & c){
    //...
    c.process();
    //...
}

以下示意性的演示使用

int main()
{

    Composite root("root");
    Composite treeNode1("treeNode1");
    Composite treeNode2("treeNode2");
    Composite treeNode3("treeNode3");
    Composite treeNode4("treeNode4");
    Leaf leat1("left1");
    Leaf leat2("left2");
    
    root.add(&treeNode1);
    treeNode1.add(&treeNode2);
    treeNode2.add(&leaf1);
    
    root.add(&treeNode3);
    treeNode3.add(&treeNode4);
    treeNode4.add(&leaf2);
    
    process(root); //处理根节点
    process(leaf2); //处理叶节点
    process(treeNode3); //处理treeNode3
  
}

假如不使用Composite 组合模式,也就是不使用以下代码

        for (auto &e : elements)
            e->process(); //多态调用

在以下代码中的process()就需要分别处理:当类型是composite或者leaf怎么处理

void Invoke(Component & c){
    //...
    c.process();
    //...
}

实际上以下代码是将内部数据结构访问封装

        for (auto &e : elements)
            e->process(); //多态调用

访问的时候使用多态的方式将树形结构的访问封装到了树形结构的内部,而不是要把树形结构暴露在外部。

5. 结构(Structure)

在这里插入图片描述

Add()、Remove()、GetChild()其实是由争议的,是放在父类Component还是Composite子类中都有不完善的地方,如果放在父类里,对于Leaf结点就比较尴尬,Leaf结点是子节点了,还可以Add()、Remove()、GetChild()吗?显然不行,不写实现内容也不舒服。所以有的实现也是我们此处实现的,就是压根不提供Add()、Remove()、GetChild()这些。换句话说在父类Component中不提供,但是在Composite子类中提供。怎么解决都有不完美的地方,但是此处的实现还是比较好些。

重点是在forall g in children;g.Operation():,Operation()中处理当前节点,接着使用多态递归调用的方式处理所有子节点

上图是比较粗略的表达了Composite 组合模式,这里的核心是用多态调用方式针对树形结构和叶子结点。

6. 要点总结

  • Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。

如果不使用那个核心代码for (auto &e : elements) e->process(); //多态调用,就需要在c.process();中去一会实现一对一(Leaf),一会实现一对多(Composite)的关系。

  • 将“客户代码与复杂的对象容器结构”解耦是Composite的核心思想,解耦之后,客户代码将与纯粹的抽象接口一一而非对象容器的内部实现结构一一发生依赖,从而更能“应对变化“。

外部无需关心内部的是树还是叶子,只统一的处理;“客户代码将与纯粹的抽象接口发生依赖”:Invoke(Component & c)中接受的参数是Component抽象类

  • Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。

7. 其他参考

C++设计模式——组合模式

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

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

相关文章

LeetCode刷题---简单组(五)

文章目录 &#x1f352;题目一 58. 最后一个单词的长度&#x1f352;解法一&#x1f352;解法二&#x1f352;题目二 66. 加一&#x1f352;解法一&#x1f352;题目三 67. 二进制求和&#x1f352;解法一 &#x1f352;题目一 58. 最后一个单词的长度 给你一个字符串 s&#x…

阿里8年经验之谈 —— Python实现性能自动化测试竟然如此简单!

一、思考❓❔ 1.什么是性能自动化测试? 性能 系统负载能力超负荷运行下的稳定性系统瓶颈自动化测试 使用程序代替手工提升测试效率性能自动化 使用代码模拟大批量用户让用户并发请求多页面多用户并发请求采集参数&#xff0c;统计系统负载能力生成报告 2.Python中的性能自动化…

【多线程相关其三】多线程使用

1.为什么要使用多线程&#xff1f; 线程在程序中是独立的、并发的执行流。与分隔的进程相比&#xff0c;进程中线程之间的隔离程度要小&#xff0c;它们共享内存、文件句柄 和其他进程应有的状态。 因为线程的划分尺度小于进程&#xff0c;使得多线程程序的并发性高。进程在执行…

winodws10系统C盘文件夹目录讲解

背景&#xff1a; 电脑安装系统一段时间后&#xff0c;发现C盘的空间越来越小&#xff0c;于是乎&#xff0c;想了解一下C盘文件目录结构&#xff0c;删除一下非必要的文件&#xff0c;同时增强一些操作系统的知识。 目前我的C盘目录如下&#xff1a; 如果开启显示隐藏文件&…

希亦内衣洗衣机和美的哪个好?内衣洗衣机对比分析

希亦内衣洗衣机与美的内衣洗衣机&#xff0c;到底哪个更胜一筹&#xff1f;当面对繁多的内衣洗衣机品牌和型号时&#xff0c;如何选购最适合自己的内衣洗衣机&#xff1f;本文将对两大家电品牌——希亦和美的的内衣洗衣机进行深度比较&#xff0c;以帮助你做出最佳决定&#xf…

【EI会议征稿】第三届结构抗震与监测检测国际学术会议(SSRMD 2024)

第三届结构抗震与监测检测国际学术会议&#xff08;SSRMD 2024&#xff09; 2024 3rd International Conference on Structural Seismic Resistance, Monitoring and Detection 随着城市化进程的深入&#xff0c;城市中的建筑越来越多。建筑也逐渐多样化&#xff0c;复杂化。…

导弹拦截(最大不上升 或 不下降子序列,dp)

算法分析&#xff1a; 1.求最长子序列 1.每次输入时&#xff0c;计算每个元素对应的序列的长度 1.向前遍历找大于当前元素的数 2.若之前元素对应长度1 大于当前长度&#xff0c;更新当前长度 2.若当前元素对应长度 大于最大长度&#xff0c;更新最大长度 2.dilworth定理 故需…

Vue.js 的核心特点

目录 1.什么是vue 2. 响应式数据绑定 3. 组件化开发 4. 虚拟DOM 5. 双向数据绑定 6. 插件扩展能力 7. 简洁易学 1.什么是vue Vue.js 是一款流行的前端 JavaScript 框架&#xff0c;用于构建用户界面。它是一个轻量级、灵活和易于上手的框架&#xff0c;广泛应用于现代 …

功能型前端项目技术栈选型

PC功能型官网技术栈选择 vue2vuexvue-routerxaxiosanimate主推&#xff1a;vue3piniavue-routertypeScriptaxiosanimate&#xff08;新技术后期踩坑多&#xff0c;成本较高&#xff09; 1.2 vue3对比vue2 团队已经熟悉 Vue 2&#xff0c;并且官网的规模不是很大&#xff0c;Vue…

一文理解什么是贝叶斯优化的随机森林

贝叶斯优化 简介 贝叶斯优化是一种启发式的全局优化方法&#xff0c;用于优化那些评估代价高昂且可能带有噪音的黑盒函数。其核心思想是&#xff1a;在每一步&#xff0c;都利用已知的函数评估来构建一个概率模型&#xff0c;预测黑盒函数在未知点上的值&#xff0c;并据此选择…

Java入门与实践

Java基础 Java入门 idea的使用 idea快捷键 crtlaltt 对选中的代码弹出环绕选项弹出层 问题描述&#xff1a;idea光标变小黑块 解决&#xff1a;误触Insert键&#xff0c;再次按Insert键即可 java基础语法 注释 //单行注释/* 多行注释 *//** 文档注释&#xff0c;可提取到…

科普|电源自动测试系统测试的项目都有哪些?

电源自动测试系统是一种用于电源性能自动测试的集成系统&#xff0c;它可以自动检测电源模块或开关电源的输入、输出、保护等各个方面。该系统通常由数据软件和各类硬件测试仪器共同组成&#xff0c;利用通讯总线、测试夹具以及其它线缆等将仪器进行连接组成整体的系统结构&…

【Axure高保真原型】百分比堆叠柱状图

今天和大家分享百分比堆叠柱状图的的原型模板&#xff0c;鼠标移入堆叠柱状图后&#xff0c;会显示数据弹窗&#xff0c;里面可以查看具体项目对应的数据和占比。那这个原型模板是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;只需要在中继器表格里维护项目数据信息…

UVC功能特性(Functional Characteristics)

目录 功能特性视频接口类视频接口子类和协议视频功能拓扑输入终端(Input Terminal)输出终端(Output Terminal)相机终端(Camera Terminal)选择单元(Selector Unit)处理单元(Processing Unit)编码单元(Encoding Unit)拓展单元(Extension Unit) 操作模型(Operational Model)视频接…

数据结构:优先级队列(堆)

概念 优先级队列是啥&#xff1f; 队列是一种先进先出 (FIFO) 的数据结构 &#xff0c;但有些情况下&#xff0c; 操作的数据可能带有优先级&#xff0c;一般出队 列时&#xff0c;可能需要优先级高的元素先出队列。 在这种情况下&#xff0c; 数据结构应该提供两个最基本的…

在线主动学习算法评估策略:prequential evaluation procedure

在线主动学习算法评估策略&#xff1a;prequential evaluation procedure 在在线主动学习领域(Online Active Learning)&#xff0c;对在线主动学习算法的评估策略有多种方法&#xff0c;而现如今常用的方法是prequential evaluation procedure(出自论文《High density-focuse…

计算机网络与技术——数据链路层

&#x1f60a;计算机网络与技术——数据链路层 &#x1f680;前言☃️基本概念&#x1f94f;封装成帧&#x1f94f;透明传输&#x1f94f;差错检测 ☃️点对点协议PPP&#x1f94f;PPP协议的特点&#x1f94f;PPP协议的帧格式&#x1f50d;PPP异步传输时透明传输&#xff08;字…

git-更新项目

进入想要更新的项目目录下&#xff0c;按住shift右击&#xff0c;点击open Git Bash here进入命令行 项目日志 git log 拉取代码 git pull origin developer 将工作区的文件添加到暂存区 git add . 查看文件状态&#xff08;更改信息&#xff09; git status 提交暂存区到…

【多线程】线程互斥 {多执行流并发执行的数据竞争问题,互斥锁的基本用法,pthread_mutex系列函数,互斥锁的原理;死锁;可重入函数和线程安全}

一、进程线程间通信的相关概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源。确切的说&#xff0c;临界资源在同一时刻只能被一个执行流访问。临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区。互斥&#xff1a;通过互…

检测和缓解SQL注入攻击

SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严&#xff0c;攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句&#xff0c;在管理员不知情的情况下实现非法操作&#xff0c;以此来实现欺骗数据库服务器执行非授权的任意查询&#…