C++设计模式结构型模式———组合模式

文章目录

  • 一、引言
  • 二、组合模式
  • 三、总结

一、引言

组合模式是一种结构型设计模式, 可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。代码实现中涉及了递归调用。组合模式与传统上的“类与类之间的组合关系”没有关联,不要混为一谈。

组合模式主要用来处理树形结构的数据,例如Windows或者类UNIX操作系统中文件的组织方式就是典型的树形结构。这里所指的数据就是这些文件或者文件夹,处理树形结构数据是指例如可以对它们进行遍历以显示目录或文件名(查看目录文件结构)、进行某些动作(例如信息统计、文件杀毒)等操作。


二、组合模式

组合模式主要是用来表达和处理树形结构数据的,作为树形结构的数据,显然要有一个树根,树根下面可以有树枝和树叶两种节点,而树枝下面又可能进一步生长出新的树枝和叶(树叶属于末端节点,其上不会生长出任何其他内容),以此类推。

例如操作系统的文件系统:

在这里插入图片描述

看一看如何用程序来把这个目录层次结构组织并输出(绘制出来),输出的结果类似于
用tree命令显示root目录产生的结果(考虑到组合模式不太好理解,可以先抛开这个模
式),这个范例的难点在于目录中还会包含更深层次的目录和文件,而这些目录和文件的名字都要求输出出来,所以实现思路应该涉及递归编程。首先创建一个用于表示文件的类FileDir,代码如下,注意代码中的注释:

// 文件相关类
class File {
public:File(const string& name) : m_sname(name) {}void ShowName(const string& lvlstr) const { // lvlstr:为了显示层次关系的缩进字符串内容cout << lvlstr << "-" << m_sname << endl;// 显示”-”代表是一个文件,属末端节点(不会再有子节点)}private:string m_sname; // 文件名
};// 目录
class Dir {
public:Dir(const string& name) : m_sname(name) {}// 目录中可以增加其他文件void AddFile(shared_ptr<File> pfile) {m_childFiles.push_back(pfile);}// 目录中可以增加其他目录void AddDir(shared_ptr<Dir> pdir) {m_childDirs.push_back(pdir);}// 显示目录名,同时也负责其下面的文件和目录名的显示工作void ShowName(const string& lvlstr) const {// (1) 输出本目录名cout << lvlstr << "+" << m_sname << endl; // 显示” + "代表是一个目录,其中会包含其他内容// (2) 输出所包含的文件名string newLvlStr = lvlstr + "   ";for (const auto& file : m_childFiles) {file->ShowName(newLvlStr + "  "); // 本目录中的文件和目录的显示,要缩进一些来显示}// (3) 输出所包含的目录名for (const auto& dir : m_childDirs) {dir->ShowName(newLvlStr + "  "); // 显示目录名,这里涉及了递归调用}}private:string m_sname; // 目录名list<shared_ptr<File>> m_childFiles; // 目录中包含的文件列表list<shared_ptr<Dir>> m_childDirs; // 目录中包含的子目录列表
};

我们给个案例使用该函数

// 创建文件
auto file1 = make_shared<File>("file1.txt");
auto file2 = make_shared<File>("file2.txt");
auto file3 = make_shared<File>("file3.txt");// 创建目录
auto dir1 = make_shared<Dir>("dir1");
auto dir2 = make_shared<Dir>("dir2");
auto dir3 = make_shared<Dir>("dir3");// 组装目录结构
dir1->AddFile(file1.get());
dir1->AddDir(dir2.get());
dir2->AddFile(file2.get());
dir2->AddDir(dir3.get());
dir3->AddFile(file3.get());// 显示目录结构
dir1->ShowName(""); // 从根目录开始显示/*输出如下
+dir1-file1.txt+dir2-file2.txt+dir3-file3.txt
*/

以一个树根为起点,可以遍历(访问)到所有该根下的树节点(既包含树枝,又包含树叶)。在本范例中,File类和Dir类的ShowName函数虽然名字相同,但它们做的事情并不相同,因为Dir类的ShowName不但要显示自身的名字,还要显示其下的文件和目录名字,而其下目录名字的显示,使用的正是递归调用,当然这里所说的递归区别于传统意义上的递归(函数调用自身),而是一种针对对象本身的递归。

上面这个范例代码中存在的问题是:为了区分文件和目录,分别创建了FileDir两个类,这种区分比较多余,为此,引人了组合模式,该模式专门针对以树形结构的形式组织对象时,不再将FileDir类单独分开,而是引人一个新的抽象类(例如FileSystem)并提供公共的接口(成员函数),而后让FileDir类分别继承自FileSystem类。看一看如何采用组合模式改造上述范例代码。


class FileSystem {
public:virtual void ShowName(int level) const = 0;virtual int Add(shared_ptr<FileSystem> pfilesys) = 0;virtual int Remove(shared_ptr<FileSystem> pfilesys) = 0;virtual ~FileSystem() {}
};// 文件相关类
class File : public FileSystem {
public:File(string name) : m_sname(name) {}virtual void ShowName(int level) const override {for (int i = 0; i < level; ++i) cout << "    ";cout << "-" << m_sname << endl;}virtual int Add(shared_ptr<FileSystem> pfilesys) override {return -1; // 文件不能添加子文件或子目录}virtual int Remove(shared_ptr<FileSystem> pfilesys) override {return -1; // 文件不能移除子文件或子目录}private:string m_sname; // 文件名
};// 目录
class Dir : public FileSystem {
public:Dir(const string& name) : m_sname(name) {}virtual void ShowName(int level) const override {// (1) 显示若干空格用于对齐for (int i = 0; i < level; ++i) cout << "    ";// (2) 输出本目录名cout << "+" << m_sname << endl;// (3) 显示的层级向下走一级level++;// (4) 输出所包含的子内容(可能是文件,也可能是子目录)for (const auto& child : m_child) {child->ShowName(level); // 显示子内容}}virtual int Add(shared_ptr<FileSystem> pfilesys) override {m_child.push_back(pfilesys);return 0;}virtual int Remove(shared_ptr<FileSystem> pfilesys) override {m_child.remove(pfilesys);return 0;}private:string m_sname; // 目录名list<shared_ptr<FileSystem>> m_child; // 目录中包含的文件和子目录
};

给一个使用案例:

// 创建文件和目录
auto root = make_shared<Dir>("root");
auto dir1 = make_shared<Dir>("dir1");
auto dir2 = make_shared<Dir>("dir2");
auto file1 = make_shared<File>("file1.txt");
auto file2 = make_shared<File>("file2.txt");
auto file3 = make_shared<File>("file3.txt");// 构建文件结构
root->Add(file1);
root->Add(dir1);
dir1->Add(file2);
dir1->Add(dir2);
dir2->Add(file3);// 显示整个文件结构
root->ShowName(0);// 移除文件和目录
dir1->Remove(file2); // 从 dir1 中移除 file2
cout << "\nAfter removing file2.txt:\n";
root->ShowName(0) ;
/* 案例输出结果
+root-file1.txt+dir1-file2.txt+dir2-file3.txtAfter removing file2.txt:
+root-file1.txt+dir1+dir2-file3.txt
*/

树形结构是一种广泛应用的数据结构,它在多种场景中都有体现,例如:

  1. 在操作系统中,文件系统的目录结构就是一个树形结构;
  2. 在各种软件工具中,菜单的层级关系也构成了一个树形结构;
  3. 在办公软件中,公司的组织架构,包括公司下的多个部门以及分公司及其部门,形成了一个树形组织结构;
  4. 在窗口应用程序中,主窗口与其包含的子窗口以及其他控件共同构成了一个树形结构;
  5. 在编程时,TreeCtrl和TreeViewUI等控件也是树形结构的实例。

组合模式非常适合处理这种树形结构,它允许通过简单的代码实现,例如执行pdir1->ShowName(0);,就能够遍历整个树形结构,并通过递归调用来一致性地处理树中的所有节点。这里的例子展示了无论节点是树枝(包含其他节点的节点)还是树叶(没有子节点的节点),都可以调用ShowName成员函数,这就是组合模式一致性处理树形结构的一个体现。

引人组合设计模式的定义:将一组对象(如文件和目录)组织成树形结构以表示“部分-整体”的层次结构(如目录中包含文件和子目录)。使得用户对单个对象(文件)和组合对象(目录)的操作/使用/处理(递归遍历并执行ShowName逻辑等)具有一致性。

总之,组合模式之所以称为结构型模式,是因为该模式提供了一个结构,可以同时包容单个对象和组合对象。组合模式发挥作用的前提是具体数据必须能以树形结构的方式表示,树中包含了单个对象和组合对象。该模式专注于树形结构中单个对象和组合对象的递归遍历(只有递归遍历才能体现出组合模式的价值),能把相同的操作(FileSystem定义的接口)应用在单个以及组合对象上,并且可以忽略单个对象和组合对象之间的差别。从模式命名上,笔者认为命名成组合模式其实并不太恰当,命名成树形模式似乎更好。

在这里插入图片描述

组合模式的一般包含3种角色。

  1. 抽象组件Component):为树枝和树叶定义接口(例如,增加、删除、获取子节点等),可以是抽象类,包含所有子类公共行为的声明或默认实现体。这里指FileSystem类。
  2. 叶子组件Leaf):用于表示树叶节点对象,这种对象没有子节点,因此抽象组件中定义的一些接口(例如Add、Remove)实际在这里没有实现的意义。这里指File类。
    • 这种叶子组件(类)对于组合模式可能不止一个,例如,若对某个目录进行杀毒,可以在抽象组件中提供KillVirus成员函数,类似ShowName,而后可以定义若干个不同的叶子类,例如定义ExeFile类并实现KillVirus专门灭杀可执行文件中的病毒,定义ImgFile类并实现KillVirus专门灭杀图像文件中的病毒等。
  3. 树枝组件Composite):用于表示一个容器(树枝)节点对象,可以包含子节点,子节点可以是树叶,也可以是树枝,其中提供了一个集合用于存储子节点(以此形成一个树形结构,可以通过递归来访问所有节点)。实现了抽象组件中定义的接口。这里指Dir类。
    • Dir类中提供的集合是一个用于存储子节点的list容器,当然用其他容器保存子节点也完全可以。

组合模式结构

在这里插入图片描述


三、总结

组合模式的主要优点包括:

  1. 客户端一致性处理:组合模式允许客户端以相同的方式对待单个对象和组合对象,无需关心它们在层次结构中的位置,从而简化了客户端代码的编写。
  2. 易于扩展:无论是添加新的叶子组件还是树枝组件,都只需添加一个新的继承自抽象组件的类,这符合开闭原则,即对扩展开放,对修改关闭。
  3. 灵活的树形结构实现:组合模式为树形结构的面向对象实现提供了一种灵活的方法,通过递归遍历单个对象和组合对象,可以处理复杂的树形结构。

在使用组合模式时,需要注意以下问题:

  1. 抽象组件的设计:为了使客户端能够一致地使用组件,抽象组件应该定义尽可能多的公共操作,并为这些操作提供默认实现。叶子组件和树枝组件可以根据需要重写这些操作。
  2. 父节点指针:根据具体业务需求,组件可能需要包含一个指向父节点的指针,这有助于在遍历节点或执行删除操作时更加方便。
  3. 遍历顺序和管理:在某些场景下,如语法分析树的表示,需要考虑节点的遍历顺序。这可能需要在添加和删除子节点时进行更复杂的管理,可能需要修改相关类的代码。

与遍历顺序相关的,还有子节点的存储问题。在示例中使用的是list这种顺序容器,但也可以根据实际情况选择其他顺序容器。C++标准库还提供了关联容器和无序容器,可以根据使用便利性和访问效率来选择合适的容器。

桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。

责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

可以使用迭代器模式来遍历组合树。也可以使用访问者模式对整个组合树执行操作。当然,使用享元模式实现组合树的共享叶节点以节省内存。组合和装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。

大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 可以通过该模式来复制复杂结构, 而非从零开始重新构造。

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

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

相关文章

Maven项目的基础配置:利用IDEA将SpringBoot的项目打包成war文件

文章目录 引言Maven项目的聚合与继承(依赖管理)把项目打包成war包其他打包配置引言 利用IDEA将SpringBoot的项目打包成war文件Maven项目的聚合与继承(依赖管理)Maven项目的聚合与继承(依赖管理) 把项目打包成war包 利用IDEA将SpringBoot的项目打包成war文件:要配置启动…

基于vue框架的的奶茶店预约订单系统3fb55(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,奶茶分类,奶茶信息 开题报告内容 开题报告 题目&#xff1a;基于Vue框架的奶茶店预约订单系统开发 一、研究背景与意义 背景 随着饮品市场的蓬勃发展&#xff0c;奶茶店作为其中的重要组成部分&#xff0c;其业务量和顾客需求持…

Interpreter 解释器模式

1 意图 给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;这个解释器使用该表示》解释语言中的句子。 2 结构 AbstactExpression 声明一个程序的解释操作&#xff0c;这个接口为抽象语法树中所有的结点所共享。TemminalExpression 实…

【数据结构】哈希思想详解

目录 前言1. unordered系列关联式容器1.1 unordered_map1.1.1 unordered_map介绍1.1.2 接口说明 1.2 unordered_set 2. 哈希概念3. 哈希冲突4. 哈希函数5. 哈希冲突解决5.1 闭散列5.1.1 闭散列的概念5.1.2 闭散列代码实现 5.2 开散列5.2.1 开散列概念5.2.2 开散列代码实现5.2.3…

变异凯撒(Crypto)

目录 解题思路 题目设计原理 总结 解题思路 从题目可以看出&#xff0c;这是凯撒密码&#xff0c;原理应该还是整体偏移&#xff0c;但是变异了。 凯撒密码只有字母的横移&#xff0c;而通过观察我们可知&#xff0c;加密密文包含大小写字母、特殊字符&#xff0c;于是猜想大…

光伏无人机踏勘,照亮光伏未来!

光伏电站选址地分散在各地&#xff0c;想要精准获取该地的地形特点与屋顶面积等信息&#xff0c;传统的人工踏勘耗时耗力且精度无法保证&#xff0c;难以满足现代光伏项目的规模快发发展需求。光伏无人机踏勘&#xff0c;照亮光伏未来&#xff01; 在光伏无人机智能踏勘设计系统…

Nvidia突袭AI江湖!悄悄发布新模型,完爆OpenAI和Anthropic?

你以为Nvidia只会造芯片&#xff1f;太天真了&#xff01;这家GPU巨头刚刚在AI语言模型领域上演了一出惊天逆袭&#xff0c;让OpenAI和Anthropic都措手不及。 没有轰轰烈烈的发布会&#xff0c;没有铺天盖地的宣传&#xff0c;Nvidia就这么静悄悄地在Hugging Face平台上扔出了一…

一. nginx学习笔记 又长又臭篇幅

目录 引言 Nginx 简介 Nginx 的特点和优势 适用场景 安装 Nginx 在 Windows 上安装 Nginx 在 Linux &#xff08;CentOS&#xff09;上安装 Nginx 基本配置 Nginx 配置文件结构 启动、停止和重载 Nginx 基本的服务器块配置 处理静态文件 设置文档根目录 配置 MIM…

go中Println和Printf的区别

Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 go中Println和Printf的区别 package mainimport ( "fmt" )//TIP To run your code, right-click the c…

Windows 部署非安装版Redis

1.下载Redis https://github.com/microsoftarchive/redis/releases 选择下载zip包&#xff0c;如Redis-x64-3.0.504.zip&#xff0c;并解压 2.启动非安装版redis服务 进入到redis目录&#xff0c;打开cmd 执行命令 redis-server.exe redis.windows.conf 3.登录redis客户端…

OpenGL入门003——使用Factory设计模式简化渲染流程

前面两节已经学会了如何使用opengl创建窗口并绘制三角形&#xff0c;我们可以看出有些步骤是固定的&#xff0c;而且都写在main.cpp&#xff0c;这一节我们将了解如何使用Factroy设计模型。将模型渲染逻辑封装在一个单独的类中&#xff0c;简化开发流程&#xff0c;且提高代码复…

Android camera2

一、序言 为了对阶段性的知识积累、方便以后调查问题&#xff0c;特做此文档&#xff01; 将以camera app 使用camera2 api进行分析。 (1)、打开相机 openCamera (2)、创建会话 createCaptureSession (3)、开始预览 setRepeatingRequest (4)、停止预览 stopRepeating (5)、关闭…

qt QColorDialog详解

1、概述 QColorDialog是Qt框架中的一个对话框类&#xff0c;专门用于让用户选择颜色。它提供了一个标准的颜色选择界面&#xff0c;其中包括基本的颜色选择器&#xff08;如调色板和颜色轮&#xff09;、自定义颜色输入区域以及预定义颜色列表。QColorDialog支持RGB、HSV和十六…

# linux系统(如ubuntu)新创建的用户终端命令无颜色,用户名等显示灰色名字而不是绿色问题解决方法

linux系统&#xff08;如ubuntu&#xff09;新创建的用户终端命令无颜色&#xff0c;用户名等显示灰色名字而不是绿色问题解决方法 一、问题描述&#xff1a; 在Linux系统中&#xff08;如ubuntu&#xff09;&#xff0c;如果新创建的用户终端命令无颜色&#xff0c;用户名等…

ASP.NET Core 路由规则 总结 mvc

资料 资料 路由服务 路由服务是在 Program.cs 中使用 builder.Services.AddRouting()注册的&#xff0c; 只是默认在 builder 之前已经注册过了&#xff0c;无需我们再次注册。 AddRouting()方法必须在 UseRouting()方法之前运行&#xff0c;它是路由的基础服务。 MapContro…

koa项目实战 == 实现注册登录鉴权

一. 项目的初始化 1 npm 初始化 npm init -y生成package.json文件: 记录项目的依赖 2 git 初始化 git init生成’.git’隐藏文件夹, git 的本地仓库 3 创建 ReadMe 文件 二. 搭建项目 1 安装 Koa 框架 npm install koa2 编写最基本的 app 创建src/main.js const Koa…

信息学科平台系统构建:Spring Boot框架深度解析

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

【JavaEE】认识进程

一、操作系统&#xff08;operating system&#xff09; 操作系统是一组做计算机资源管理的软件的统称&#xff0c;它能够把一个计算机上的所有硬件资源和软件资源都管理好&#xff1a;能够管理好各种硬件资源&#xff0c;让他们很好的相互配合&#xff0c;能够管理好各种软件…

Chromium Mojo(IPC)进程通信演示 c++(4)

122版本自带的mojom通信例子仅供学习参考&#xff1a; codelabs\mojo_examples\01-multi-process 其余定义参考文章&#xff1a; Chromium Mojo(IPC)进程通信演示 c&#xff08;2&#xff09;-CSDN博客 01-mojo-browser.exe 与 01mojo-renderer.exe进程通信完整例子。 一、…

像`npm i`作为`npm install`的简写一样,使用`pdm i`作为`pdm install`的简写

只需安装插件pdm-plugin-i即可&#xff1a; pdm plugin add pdm-plugin-i 然后就可以愉快地pdm i了&#xff0c;例如&#xff1a; git clone https://github.com/waketzheng/fast-dev-cli cd fast-dev-cli python -m pip install --user pipx pipx install pdm pdm plugin a…