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

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 nodesfor (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 nodesfor (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.hqwc.cn/news/156784.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一条 SQL 是如何在 MyBatis 中执行的

前言 MyBatis 执行 SQL 的核心接口为 SqlSession 接口&#xff0c;该接口提供了一些 CURD 及控制事务的方法&#xff0c;另外还可以通过 SqlSession 先获取 Mapper 接口的实例&#xff0c;然后通过 Mapper 接口执行 SQL&#xff0c;Mapper 接口方法的执行最终还是委托到 SqlSe…

206. 反转链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;看看是李XX还是李歘歘 &#x1f3c6; &#x1f33a;每天分享一些包括但不限于计算机基础、算法等相关的知识点&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&#x1f4d6;是你想要的&#x1f497; ⛽️今…

【计算机网络】同源策略及跨域问题

1. 同源策略 同源策略是一套浏览器安全机制&#xff0c;当一个源的文档和脚本&#xff0c;与另一个源的资源进行通信时&#xff0c;同源策略就会对这个通信做出不同程度的限制。 同源策略对 同源资源 放行&#xff0c;对 异源资源 限制。因此限制造成的开发问题&#xff0c;称…

故障诊断模型 | Maltab实现BiLSTM双向长短期记忆神经网络故障诊断

文章目录 效果一览文章概述模型描述源码设计参考资料效果一览 文章概述 故障诊断模型 | Maltab实现BiLSTM双向长短期记忆神经网络故障诊断 模型描述 利用各种检查和测试方法,发现系统和设备是否存在故障的过程是故障检测;而进一步确定故障所在大致部位的过程是故障定位。故障…

如何创建一个react项目

文章目录 前言前言打开小黑窗口npm init vite后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;react.js &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&am…

【NLP】什么是语义搜索以及如何实现 [Python、BERT、Elasticsearch]

语义搜索是一种先进的信息检索技术&#xff0c;旨在通过理解搜索查询和搜索内容的上下文和含义来提高搜索结果的准确性和相关性。与依赖于匹配特定单词或短语的传统基于关键字的搜索不同&#xff0c;语义搜索会考虑查询的意图、上下文和语义。 语义搜索在搜索结果的精度和相关…

机器人入门(四)—— 创建你的第一个虚拟小车

机器人入门&#xff08;四&#xff09;—— 创建你的第一个虚拟小车 一、小车建立过程1.1 dd_robot.urdf —— 建立身体1.2 dd_robot2.urdf —— 添加轮子1.3 dd_robot3.urdf —— 添加万向轮1.4 dd_robot4.urdf —— 添加颜色1.5 dd_robot5.urdf —— 添加碰撞检测(Collision …

【机器学习可解释性】4.SHAP 值

机器学习可解释性 1.模型洞察的价值2.特征重要性排列3.部分依赖图4.SHAP 值5.SHAP值的高级使用 正文 理解各自特征的预测结果&#xff1f; 介绍 您已经看到(并使用)了从机器学习模型中提取一般解释技术。但是&#xff0c;如果你想要打破模型对单个预测的工作原理? SHAP 值…

Linux内核是如何创建进程?

目录 1.Linux如何创建进程 2.fork函数原理 2.1 fork函数原型 2.2 fork函数实现原理 2.3 父子进程虚拟地址空间&#xff08;mm_struct&#xff09;之间的关系 2.4 写时拷贝&#xff08;copy-on-write&#xff09;技术 2.5 父子进程如何共享文件&#xff08;files_struct&…

Bootstrap

一、介绍 Bootstrap 是一个流行的开源前端框架&#xff0c;用于快速构建响应式和移动设备优先的网页。它由 Twitter 设计师和开发人员创建&#xff0c;提供了一套功能强大且易于使用的 CSS、JavaScript 组件和样式&#xff0c;可用于构建现代化的网站和应用程序。Bootstrap 是受…

图数据库Neo4j概念、应用场景、安装及CQL的使用

一、图数据库概念 引用Seth Godin的说法&#xff0c;企业需要摒弃仅仅收集数据点的做法&#xff0c;开始着手建立数据之间的关联关系。数据点之间的关系甚至比单个点本身更为重要。 传统的**关系数据库管理系统(RDBMS)**并不擅长处理数据之间的关系&#xff0c;那些表状数据模…

【Cocos新手进阶】使用cocos 的预制体创建动态的滚动框组件。

本篇文章主要讲解&#xff0c;使用cocos 游戏引擎制作动态生成的滚动框实例教程。 日期&#xff1a;2023年11月1日 作者&#xff1a;任聪聪 引擎版本&#xff1a;2.4.3 至 2.4.11 关于预制体的说明和概念 cocos中的预制体的作用是能够让你使用数据的形式进行控制界面的变化&am…