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


使用场景

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

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

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

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

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

我们可以创建一个文件类,用来保存普通文件,然后再创建一个目录类,这个目录类中有一个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;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值