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

2023-11-16


使用场景

组合模式和类与类之间的组合是不同的概念。

组合模式主要用来处理树形结构的数据,如果要表达的数据不是树形结构,就不太适合组合模式。

比如我们有一个目录结构:
在这里插入图片描述

这个目录我们把它绘制成树形结构:
在这里插入图片描述

如何利用程序把这个目录结构给绘制出来呢?

我们可以创建一个文件类,用来保存普通文件,然后再创建一个目录类,这个目录类中有一个list来保存子目录和子文件:

namespace hjl_project1
{	
	//文件相关类
	class File
	{
	public:
		//构造函数
		File(string name) :m_sname(name) {}

		//显示文件名
		void ShowName(string lvlstr) //lvlstr:为了显示层次关系的缩进字符串内容
		{
			cout << lvlstr << "-" << m_sname << endl; //显示“-”代表是一个文件,属末端节点(不会再有子节点)
		}

	private:
		string m_sname; //文件名
	};

	//目录相关类
	class Dir
	{
	public:
		Dir(string name) :m_sname(name) {}

	public:
		//目录中可以增加其他文件
		void AddFile(File* pfile)
		{
			m_childFile.push_back(pfile);
		}
		//目录中可以增加其他目录
		void AddDir(Dir* pdir)
		{
			m_childDir.push_back(pdir);
		}

		//显示目录名,同时也要负责其下面的文件和目录名的显示工作
		void ShowName(string lvlstr)//lvlstr:为了显示层次关系的缩进字符串内容
		{
			//(1)输出本目录名
			cout << lvlstr << "+" << m_sname << endl; //显示“+”代表是一个目录,其中会包含其他内容

			//(2)输出所包含的文件名
			lvlstr += "    "; //本目录中的文件和目录的显示,要缩进一些来显示
			for (auto iter = m_childFile.begin(); iter != m_childFile.end(); ++iter)
			{
				(*iter)->ShowName(lvlstr); //显示文件名
			}

			//(3)输出所包含的目录名
			for (auto iter = m_childDir.begin(); iter != m_childDir.end(); ++iter)
			{
				(*iter)->ShowName(lvlstr); //显示目录名,这里涉及了递归调用 
			}
		}

	private:
		string m_sname; //目录名
		list<File*> m_childFile; //目录中包含的文件列表
		list<Dir*> m_childDir; //目录中包含的子目录列表
	};

}

然后创建出目录和文件,在父目录下添加子目录和文件即可:

int main()
{
	using namespace hjl_project1;
	//(1)创建各种目录,文件对象
	Dir *pdir1 = new Dir("root");
	//----
	File* pfile1 = new  File("common.mk");
	File* pfile2 = new  File("config.mk");
	File* pfile3 = new  File("makefile");
	//-----
	Dir* pdir2 = new Dir("app");
	File* pfile4 = new  File("nginx.c");
	File* pfile5 = new  File("ngx_conf.c");
	//-----
	Dir* pdir3 = new Dir("signal");
	File* pfile6 = new  File("ngx_signal.c");
	//-----
	Dir* pdir4 = new Dir("_include");
	File* pfile7 = new  File("ngx_func.h");
	File* pfile8 = new  File("ngx_signal.h");

	//(2)构造树形目录结构
	pdir1->AddFile(pfile1);
	pdir1->AddFile(pfile2);
	pdir1->AddFile(pfile3);
	//----
	pdir1->AddDir(pdir2);
		pdir2->AddFile(pfile4);
		pdir2->AddFile(pfile5);
	//----
	pdir1->AddDir(pdir3);
		pdir3->AddFile(pfile6);
	//----
	pdir1->AddDir(pdir4);
		pdir4->AddFile(pfile7);
		pdir4->AddFile(pfile8);


	//(3)输出整个目录结构,只要通过根目录的ShowName方法即可,每个子目录都有自己的ShowName方法负责自己旗下的文件和目录显示。
	pdir1->ShowName(""); //缩进字符刚开始可以为空
	pdir2->ShowName("");

	//(4)释放资源
	delete pfile8;
	delete pfile7;
	delete pdir4;
	//----
	delete pfile6;
	delete pdir3;
	//----
	delete pfile5;
	delete pfile4;
	delete pdir2;
	//----
	delete pfile3;
	delete pfile2;
	delete pfile1;
	delete pdir1;
	
	return 0;
}

在这里插入图片描述

上面的例子存在一些问题,为了区分目录和文件我们引入了两种类,这种区分是比较多余的。

在组合模式中,不再将目录和文件两种类单独分开,而是引入新的抽象类提供公共接口,目录和文件两种类继承这个抽象类。

namespace hjl_project2
{
	//抽象父类FileSystem(抽象接口)
	class FileSystem
	{
	public:
		virtual void ShowName(int level) = 0; //显示名字,参数level用于表示显示的层次,用于显示对齐
		virtual int Add(FileSystem* pfilesys) = 0; //向当前目录中增加文件或者子目录
		virtual int Remove(FileSystem* pfilesys) = 0; //从当前目录中移除文件或者子目录
		
		virtual ~FileSystem() {} //做父类时析构函数应该为虚函数
	};

	//文件相关类
	class File :public FileSystem
	{
	public:
		//构造函数
		File(string name) :m_sname(name) {}
		//显示名
		virtual void ShowName(int level)
		{
			for (int i = 0; i < level; ++i)
			{
				cout << "    "; //显示若干个空格用于对齐
			}
			cout << "-" << m_sname << endl;
		}
		virtual int Add(FileSystem* pfilesys)
		{
			return -1;
		}
		virtual int Remove(FileSystem* pfilesys)
		{
			return -1;
		}

	private:
		string m_sname; //文件名
	};

	//目录相关类
	class Dir :public FileSystem
	{
	public:
		//构造函数
		Dir(string name) :m_sname(name) {}

		//显示名字
		virtual void ShowName(int level)
		{
			//(1)显示若干个空格用于对齐
			for (int i = 0; i < level; ++i) { cout << "    "; }
			//(2)输出本目录名
			cout << "+" << m_sname << endl;
			//(3)显示的层级向下走一级
			level++;
			//(4)输出所包含的子内容(可能是文件,也可能是目录)
			//遍历目录中的文件和子目录
			for (auto iter = m_child.begin(); iter != m_child.end(); ++iter)
			{
				(*iter)->ShowName(level);
			}
		}

		virtual int Add(FileSystem* pfilesys)
		{
			m_child.push_back(pfilesys);
			return 0;
		}

		virtual int Remove(FileSystem* pfilesys)
		{
			m_child.remove(pfilesys);
			return 0;
		}

	private:
		string m_sname; //文件名
		list<FileSystem*> m_child; //目录中包含的文件或者其他目录列表
	};
}
int main()
{
	using namespace hjl_project2;

	//(1)创建各种目录,文件对象
	FileSystem* pdir1 = new Dir("root");
	//----
	FileSystem* pfile1 = new  File("common.mk");
	FileSystem* pfile2 = new  File("config.mk");
	FileSystem* pfile3 = new  File("makefile");
	//-----
	FileSystem* pdir2 = new Dir("app");
	FileSystem* pfile4 = new  File("nginx.c");
	FileSystem* pfile5 = new  File("ngx_conf.c");
	//-----
	FileSystem* pdir3 = new Dir("signal");
	FileSystem* pfile6 = new  File("ngx_signal.c");
	//-----
	FileSystem* pdir4 = new Dir("_include");
	FileSystem* pfile7 = new  File("ngx_func.h");
	FileSystem* pfile8 = new  File("ngx_signal.h");

	//(2)构造树形目录结构
	pdir1->Add(pfile1);
	pdir1->Add(pfile2);
	pdir1->Add(pfile3);
	//----
	pdir1->Add(pdir2);
	pdir2->Add(pfile4);
	pdir2->Add(pfile5);
	//----
	pdir1->Add(pdir3);
	pdir3->Add(pfile6);
	//----
	pdir1->Add(pdir4);
	pdir4->Add(pfile7);
	pdir4->Add(pfile8);


	//(3)输出整个目录结构,只要通过根目录的ShowName方法即可,每个子目录都有自己的ShowName方法负责自己旗下的文件和目录显示。
	pdir1->ShowName(0); //缩进字符刚开始可以为空
	//pdir2->ShowName("");

	//(4)释放资源
	delete pfile8;
	delete pfile7;
	delete pdir4;
	//----
	delete pfile6;
	delete pdir3;
	//----
	delete pfile5;
	delete pfile4;
	delete pdir2;
	//----
	delete pfile3;
	delete pfile2;
	delete pfile1;
	delete pdir1;

	return 0;
}

在这里插入图片描述

在这里插入图片描述


组合模式的定义

将一组对象(文件和目录)组织成属性结构以表示“部分整体”的层次结构(目录中包含文件和子目录),是的用户对蛋哥对象(文件)和组合对象(目录)的操作/使用/处理具有一致性(不用区分树叶和树枝,两者都有相同的接口)。

使用组合模式的前提是,具体的数据必须能够以树形结构的方式表示,数中包含了单个对象和组合对象。该模式专注于树形结构中蛋哥对象和组合对象的递归遍历。

主要有三种角色:

  1. 抽象组件:上面的filesystem类
  2. 叶子组件:file类
  3. 树枝组件:dir类,可以包含子节点,子节点可以是树枝也可以是树叶

组合模式的优点:

  1. 简化了代码的书写。
  2. 符合开闭原则,后续增加叶子组件或者树枝组件,只需要创建新类并继承抽象组件即可。
  3. 方便处理复杂的树形结构。

安全组合模式

上面的组合模式属于透明组合模式:在抽象组件中声明了所有用于管理和访问子节点的成员函数的实现手段。这也就意味着叶子组件和树枝组件都需要实现他们。

而安全组合模式:抽象组件中用于管理和访问子节点的成员函数被转移到了树枝组件中。

namespace hjl_project3
{
	class Dir;
	//抽象父类FileSystem(抽象接口)
	class FileSystem
	{
	public:
		virtual void ShowName(int level) = 0; //显示名字,参数level用于表示显示的层次,用于显示对齐

		virtual int countNumOfFiles() = 0; //统计目录下包含的文件个数
		
		virtual Dir* ifCompositeObj() { return nullptr; } //判断是否是一个树枝(组合对象)
		virtual ~FileSystem() {} //做父类时析构函数应该为虚函数
	};

	//文件相关类
	class File :public FileSystem
	{
	public:
		//构造函数
		File(string name) :m_sname(name) {}
		//显示名
		virtual void ShowName(int level)
		{
			for (int i = 0; i < level; ++i)
			{
				cout << "    "; //显示若干个空格用于对齐
			}
			cout << "-" << m_sname << endl;
		}		

		virtual int countNumOfFiles()//统计目录下包含的文件个数
		{
			return 1; //文件节点,做数量统计时按1计算
		}

	private:
		string m_sname; //文件名
	};

	//目录相关类
	class Dir :public FileSystem
	{
	public:
		//构造函数
		Dir(string name) :m_sname(name) {}

		//显示名字
		virtual void ShowName(int level)
		{
			//(1)显示若干个空格用于对齐
			for (int i = 0; i < level; ++i) { cout << "    "; }
			//(2)输出本目录名
			cout << "+" << m_sname << endl;
			//(3)显示的层级向下走一级
			level++;
			//(4)输出所包含的子内容(可能是文件,也可能是目录)
			//遍历目录中的文件和子目录
			for (auto iter = m_child.begin(); iter != m_child.end(); ++iter)
			{
				(*iter)->ShowName(level);
			}
		}

		int Add(FileSystem* pfilesys)
		{
			m_child.push_back(pfilesys);
			return 0;
		}

		int Remove(FileSystem* pfilesys)
		{
			m_child.remove(pfilesys);
			return 0;
		}

		virtual Dir* ifCompositeObj() { return this; }

		virtual int countNumOfFiles()//统计目录下包含的文件个数
		{
			int iNumOfFiles = 0;
			for (auto iter = m_child.begin(); iter != m_child.end(); ++iter)
			{
				iNumOfFiles += (*iter)->countNumOfFiles(); //递归调用
			}
			return iNumOfFiles;
		}

	private:
		string m_sname; //文件名
		list<FileSystem*> m_child; //目录中包含的文件或者其他目录列表
	};

}


int main()
{
	using namespace hjl_project3;

	//(1)创建各种目录,文件对象
	Dir* pdir1 = new Dir("root");
	//----
	FileSystem* pfile1 = new  File("common.mk");
	FileSystem* pfile2 = new  File("config.mk");
	FileSystem* pfile3 = new  File("makefile");
	//-----
	Dir* pdir2 = new Dir("app");
	FileSystem* pfile4 = new  File("nginx.c");
	FileSystem* pfile5 = new  File("ngx_conf.c");
	//-----
	Dir* pdir3 = new Dir("signal");
	FileSystem* pfile6 = new  File("ngx_signal.c");
	//-----
	Dir* pdir4 = new Dir("_include");
	FileSystem* pfile7 = new  File("ngx_func.h");
	FileSystem* pfile8 = new  File("ngx_signal.h");

	//(2)构造树形目录结构
	pdir1->Add(pfile1);
	pdir1->Add(pfile2);
	pdir1->Add(pfile3);
	//----
	pdir1->Add(pdir2);
	pdir2->Add(pfile4);
	pdir2->Add(pfile5);
	//----
	pdir1->Add(pdir3);
	pdir3->Add(pfile6);
	//----
	pdir1->Add(pdir4);
	pdir4->Add(pfile7);
	pdir4->Add(pfile8);


	//(3)输出整个目录结构,只要通过根目录的ShowName方法即可,每个子目录都有自己的ShowName方法负责自己旗下的文件和目录显示。
	pdir1->ShowName(0); //缩进字符刚开始可以为空
	//pdir2->ShowName("");

	cout<<"文件个数:"<<pdir1->countNumOfFiles()<<endl;

	//(4)释放资源
	delete pfile8;
	delete pfile7;
	delete pdir4;
	//----
	delete pfile6;
	delete pdir3;
	//----
	delete pfile5;
	delete pfile4;
	delete pdir2;
	//----
	delete pfile3;
	delete pfile2;
	delete pfile1;
	delete pdir1;

	return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++设计模式---组合模式 的相关文章

随机推荐

  • vscode安装

    博客 https zhuanlan zhihu com p 106357123 https code visualstudio com download
  • [创业之路-47] :动态股权机制 -2- 多轮融资股权稀释后,大股东保留控制权的常见套路

    正常来说一个蒸蒸日上的公司或者被看好的公司 都要面临融资的问题 融资之后 股权就会发生比较大的变化 多轮融资之后呢 再次引进新的合伙人呢 那时候的股权肯定低于51 甚至低于34 这时候靠什么掌控公司 1 有限合伙平台模式GP VS LP 同
  • windows安装pm2

    pm2是开源的进程管理器 可用于管理您的nodejs项目等 前段时间看到同事使用到此工具 今日特地学习安装使用 安装流程如下 1 确保你电脑安装了npm 可以通过npm v命令查看 npm v 2 安装pm2 命令 npm install
  • 程序员代码打字练习题库

    创建一个新的txt文件 将本文内容全选 粘贴 保存 然后导入金山打字通 即可练习程序员常用词汇 推荐使用金山打字通 英文文章练习 自定义课程 导入txt文件 保存 接下来你就可以联系打字了 path class classpath publ
  • 绕过protected方法的调用(包级共享)

    A类在a包 package a import b B public class A protected void sys System out println A protected sys方法 public static void mai
  • [Activiti 资料]Activiti 画图工具(activiti-designer,actiBPM,activiti-app)

    1 eclipse eclipse的画流程工具 activiti designer 1 1 直接下载Eclipse 或者下载已经安装了activiti designer的eclipse 既然你下载到了 怎么也礼节性的感谢一下哈 链接 htt
  • ENVI入门系列教程---二、图像分析---13. 遥感动态监测

    every blog every motto God helps those who help themselves https blog csdn net weixin 39190382 type blog 0 前言 遥感变化检测就是从不
  • EasyExcel,让excel导入导出更加简单

    EasyExcel EasyExcel是一个基于Java的简单 省内存的读写Excel的开源项目 在尽可能节约内存的情况下支持读写百M的Excel github地址 https github com alibaba easyexcel JA
  • 数据降维算法

    文章目录 效果一览 文章概述 部分源码 参考资料 效果一览 文章概述 数据降维算法 Matlab 基于局部费歇尔判别 LFDA 的分类数据降维可视化 部分源码
  • OpenCV Android以及扩展模块opencv_contrib的编译

    OpenCV和OpenCV contrib模块4 5 x版本的编译 环境准备 Ubuntu 16 04 1 环境变量 export ANDROID HOME work android sdk export ANDROID NDK HOME
  • 【allegro 17.4软件操作保姆级教程一】软件操作环境设置

    个人主页 highman110 作者简介 一名硬件工程师 持续学习 不断记录 保持思考 输出干货内容 目录 1操作环境准备 1 1单位设置 1 2画布面积设置 1 3软件显示设置 1 4布局显示设置 1 5格点设置 1 6大十字光标设置 1
  • 什么场景应该用 MongoDB ?

    月初在云栖社区上发起了一个 MongoDB 使用场景及运维管理问题交流探讨 的技术话题 有近5000人关注了该话题讨论 这里就 MongoDB 的使用场景做个简单的总结 谈谈什么场景该用 MongoDB 很多人比较关心 MongoDB 的适
  • 华为OD机试(JAVA)真题 2023(汽水瓶\随机数\进制转换)

    系列文章目录 文章目录 系列文章目录 前言 一 1 汽水瓶 二 明明的随机数 前言 一 1 汽水瓶 某商店规定 三个空汽水瓶可以换一瓶汽水 允许向老板借空汽水瓶 但是必须要归还 小张手上有n个空汽水瓶 她想知道自己最多可以喝到多少瓶汽水 数
  • 运维岗位面试被问到的问题

    一 tcp ip 三次握手具体过程 二 静态路由动态路由里面有哪些协议 三 ip地址分类 四 iptabled 五 linu系统和windows系统的区别 六 linux软连接与硬链接的区别 七 Linux命令 八 如何将一个用户添加到某一
  • cocosCreator 之 ScrollView

    版本 3 4 0 参考 ScrollView组件 简介 ScrollView组件作为滚动容器来使用 它的实现通过ScrollBar组件来展示内容的位置和Mask组件显示指定区域 来保证有限的区域内显示更多的内容 它的构成部分 ScrollB
  • 史上功能最全的Java权限认证框架

    文章目录 Sa Token是什么 Sa Token 能做什么 代码示例 官网地址 sa token 使用示例 SpringBoot 环境 1 创建项目 2 设置jar包依赖 3 配置文件 4 启动类 5 运行 Sa Token是什么 sa
  • LeetCode 101:和你一起你轻松刷题(python版) 第 1 章 题目分类

    LeetCode 101 和你一起你轻松刷题 python版 注 作者 高畅 Chang Gao 原书为c 版本 解题思路清晰 知识点全面 是一本好书 翻译成python版本的解法可能未必是最优解法 由于本人是新手小白 算法实现是第一步 优
  • stm32+lwip(四):网页服务器测试

    ST官方有lwip的例程 下载地址如下 https www st com content st com en products embedded software mcus embedded software stm32 embedded
  • ubuntu 22.04 升级openssh9.4p1 ,openssl3.1.2,zlib1.2.13

    参考 https blog csdn net weixin 37534043 article details 120822689 https blog csdn net xujiamin0022016 article details 878
  • C++设计模式---组合模式

    文章目录 使用场景 组合模式的定义 安全组合模式 使用场景 组合模式和类与类之间的组合是不同的概念 组合模式主要用来处理树形结构的数据 如果要表达的数据不是树形结构 就不太适合组合模式 比如我们有一个目录结构 这个目录我们把它绘制成树形结构