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

news/2025/2/13 23:08:31/

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.ppmy.cn/news/1186887.html

相关文章

vscode在新窗口打开文件夹

点击左槐娃下角中更多设置选项 VS Code怎么设置在上的新窗口中打开文件夹 2 弹出了下拉蚂阅菜单选择settings选项 VS Code怎么设置在上的新窗口中打开文件夹 3 点击左侧中new widow选项 VS Code怎么设置在上的新窗口中打开文件夹 4 点击open folders in new window选项 VS …

Springmvc 讲解(1)

文章目录 前言一、SpringMvc1、简介2、核心组件和调用流程2.1 涉及组件的理解 3、小案例快速体验3.1场景需求3.1.1 导入依赖3.1.2 controller声明3.1.3 核心配置类3.1.4 环境搭建3.1.6 配置tomcat3.1.7 测试 二、SpringMvc 接收参数1.路径设置注解2、param接收参数四种类型2.1 …

springboot整合日志,并在本地查看

目录 1.导入依赖 2.编写配置 3.使用 4.验证 5.打印错误信息 1.导入依赖 <!-- logback&#xff0c;向下兼容log4j,还支持SLF4J--> <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId> </depen…

10.30 知识总结(标签分类、css介绍等)

一、 标签的分类 1.1 单标签 img br hr <img /> 1.2 双标签 a h p div <a></a> 1.3 按照标签属性分类 1.3.1 块儿标签 即自己独自占一行 h1-h6 p div 1.3.2 行内(内联)标签 即自身文本有多大就占多大 a span u i b s 二、 标签的嵌套 标签之间是可以互相…

JAVA-图形化编程——排序

import java.awt.*; import java.awt.event.ActionEvent; import java.lang.reflect.Array; import javax.swing.*; import java.util.*;public class 排序 {public static void main(String args[]) {//初始化控件们JFrame jf new JFrame("小排序");jf.setLayout(n…

亚马逊美国加拿大电动移动设备合规标准是什么?如何办理?

亚马逊美国站电动移动设备合规标准是什么&#xff1f; 加拿大站电动移动设备合规标准 办理流程&#xff1a; 1.填写申请表 2.提供产品的资料&#xff08;说明书&#xff0c;电路原理图&#xff0c;如是多个型号的&#xff0c;提供型号差异列表&#xff09; 3.寄样 4.测试 …

vue3学习(十五)--- Pinia状态管理器

文章目录 安装引入初始化仓库Store页面使用state1. 直接修改state2. 批量修改State的值 $patch对象形式3. 批量修改State的值 $patch函数形式4. 通过actions修改 使用方法直接在实例调用解构store gettersactions 同步和异步同步异步 常见API$reset()$subscribe$onAction pinia…

ES 8.x新特性一览(完整版)

一、看点 在 2022 年 2 月 11 日&#xff0c;Elasticsearch&#xff08;ES&#xff09;正式发布了 8.0 版本&#xff0c;而截止到 2023 年 10 月&#xff0c;历经一年半时间&#xff0c;ES官方已经连续发布了多个版本&#xff0c;最新版本为 8.10.4。这一系列的更新引入了众多引…