C++ 设计模式——组合模式
组合模式(Composite Pattern)是一种结构性设计模式,允许将一组对象组织成树形结构以表示“部分-整体”的层次结构。使得用户对单个对象和组合对象的操作/使用/处理具有一致性。组合模式使得客户端可以以统一的方式对待单个对象和组合对象,从而简化了客户端代码的复杂性。
1. 主要组成成分
- 抽象组件(Component):声明了叶子和组合对象的共同接口。
- 叶子组件(Leaf):实现了组件接口,代表树中的叶子节点。
- 组合组件(Composite):实现了组件接口,定义了叶子和子组件的行为,存储子组件。
2. 逐步构建透明组合模式
透明组合模式允许客户端以统一的方式处理单个对象和组合对象。通过在基类中定义 Add
和 Remove
方法,组合对象可以自由添加和删除子对象。这种模式适用于需要处理树形结构的场景,提供了良好的灵活性和可扩展性。下面是如何逐步构建透明组合模式的示例。
1. 定义抽象组件(Component)
首先,定义一个抽象基类 FileSystem
,声明所有具体组件的接口。
//抽象父类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() {} //做父类时析构函数应该为虚函数
};
2. 实现叶子组件(Leaf)
接下来,创建一个表示文件的类 File
,它继承自 FileSystem
。
//文件相关类
class File :public FileSystem
{
public:
//构造函数
File(string name) :m_sname(name) {}
//显示名字
void ShowName(int level)
{
for (int i = 0; i < level; ++i) { cout << " "; } //显示若干个空格用与对齐
cout << "-" << m_sname << endl;
}
virtual int Add(FileSystem* pfilesys)
{
//文件中其实是不可以增加其他文件或者目录的,所以这里直接返回-1,无奈的是父类中定义了该接口,所以子类中也必须实现该接口
return -1;
}
virtual int Remove(FileSystem* pfilesys)
{
//文件中不包含其他文件或者目录,所以这里直接返回-1
return -1;
}
private:
string m_sname; //文件名
};
3. 实现组合组件(Composite)
接着,创建一个表示目录的类 Dir
,它也继承自 FileSystem
。
//目录相关类
class Dir :public FileSystem
{
public:
//构造函数
Dir(string name) :m_sname(name) {}
//显示名字
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);
}//end for
}
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; //目录中包含的文件或其他目录列表
};
4. 主函数(Main)
在 main
函数中,创建文件和目录对象,并构建树形结构。
int main()
{
Dir* root = new Dir("root");
FileSystem* file1 = new File("common.mk");
FileSystem* file2 = new File("config.mk");
Dir* appDir = new Dir("app");
FileSystem* file3 = new File("nginx.c");
// 构建树形结构
root->Add(file1);
root->Add(file2);
root->Add(appDir);
appDir->Add(file3);
// 显示整个目录结构
root->ShowName(0);
// 释放资源
delete file3;
delete appDir;
delete file2;
delete file1;
delete root;
return 0;
}
运行上述代码,输出结果如下:
+root
-common.mk
-config.mk
+app
-nginx.c
透明组合模式 UML 图
透明组合模式 UML 图解析
- Component (抽象组件): 为树枝和树叶定义接口(例如,增加、删除、获取子节点等),可以是抽象类,包含所有子类公共行为的声明或默认实现体。这里指
FileSystem
类。 - Leaf (叶子组件): 用于表示树叶节点对象,这种对象没有子节点,因此抽象组件中定义的一些接口(例如
Add
、Remove
)在这里没有实现的意义。这里指File
类。 - Composite (树枝组件): 用于表示一个容器(树枝)节点对象,可以包含子节点,子节点可以是树叶,也可以是树枝,其中提供了一个集合用于存储子节点(以此形成一个树形结构,可以通过递归来访问所有节点)。实现了抽象组件中定义的接口。这里指
Dir
类。
透明组合模式的优点
- 一致的接口:客户端可以使用相同的方式处理单个对象和组合对象。
- 简化代码:减少了客户端代码的复杂性,便于管理和扩展。
- 灵活性:可以轻松地添加新的叶子或组合类。
透明组合模式的缺点
- 设计复杂性:需要设计一个抽象组件接口,可能导致过多的类。
- 性能开销:在某些实现中,可能会有额外的性能开销,特别是在树结构较深时。
透明组合模式的适用场景
- 需要表示“部分-整体”层次结构的场景,例如文件系统、组织结构等。
- 客户端需要统一处理单个对象和组合对象的场景。
3. 逐步构建安全组合模式
安全组合模式通过类型安全的方式管理树形结构中的对象,确保只有组合对象可以添加子对象。通过实现 ifCompositeObj
方法,能够明确区分叶子和组合对象,从而避免不当操作。此模式适用于需要严格管理树形结构的场景。以下是逐步构建安全组合模式的示例。
1. 定义抽象组件(Component)
首先,定义一个抽象基类 FileSystem
,它声明了所有具体组件的接口。
#include <iostream>
#include <list>
#include <string>
class Dir; // 前向声明
//抽象父类FileSystem(抽象接口)
class FileSystem
{
public:
virtual void ShowName(int level) = 0; //显示名字,参数level用于表示显示的层级,用于显示对齐
virtual ~FileSystem() {}//做父类时析构函数应该为虚函数
virtual Dir* ifCompositeObj() { return nullptr; }
virtual int countNumOfFiles() = 0; //统计目录下包含的文件个数
};
2. 实现叶子组件(Leaf)
接下来,创建一个表示文件的类 File
,它继承自 FileSystem
。
//文件相关类
class File :public FileSystem
{
public:
//构造函数
File(string name) :m_sname(name) {}
//显示名字
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; //文件名
};
3. 实现组合组件(Composite)
接着,创建一个表示目录的类 Dir
,它也继承自 FileSystem
。
// 目录类
class Dir :public FileSystem
{
//该类内容基本没变,把Add和Remove成员函数前面的virtual修饰符去掉即可。
public:
//构造函数
Dir(string name) :m_sname(name) {}
//显示名字
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);
}//end for
}
/*virtual*/ int Add(FileSystem* pfilesys)
{
m_child.push_back(pfilesys);
return 0;
}
/*virtual*/ 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; //目录中包含的文件或其他目录列表
};
4. 主函数(Main)
在 main
函数中,创建文件和目录对象,并构建树形结构。
int main() {
Dir* root = new Dir("root");
FileSystem* file1 = new File("common.mk");
FileSystem* file2 = new File("config.mk");
Dir* appDir = new Dir("app");
FileSystem* file3 = new File("nginx.c");
// 构建树形结构
root->Add(file1);
root->Add(file2);
root->Add(appDir);
appDir->Add(file3);
// 显示整个目录结构
root->ShowName(0);
std::cout << "文件的数量为:" << root->countNumOfFiles() << std::endl;
// 释放资源
delete file3;
delete appDir;
delete file2;
delete file1;
delete root;
return 0;
}
运行上述代码,输出结果如下:
+root
-common.mk
-config.mk
+app
-nginx.c
文件的数量为:3
安全组合模式 UML 图
安全组合模式 UML 图解析
- Component (抽象组件): 定义树枝和树叶的接口,包含公共行为的声明。
- Leaf (叶子组件): 表示没有子节点的对象,通常不实现
Add
和Remove
方法。 - Composite (树枝组件): 包含子节点,能够递归地调用子节点的方法。
安全组合模式的优点
- 类型安全:通过类型检查,确保只有组合对象可以添加子对象。
- 清晰的责任分配:各类的功能分离更加明确,便于维护。
安全组合模式的缺点
- 复杂性增加:实现安全组合模式可能需要更多的代码和逻辑。
- 不够灵活:如果需要动态改变对象的行为,可能会受到限制。
安全组合模式的适用场景
- 需要确保组合对象的类型安全,避免非法操作的场景。
- 需要明确区分叶子和组合对象的场景。
总结
组合模式是一种强大的设计模式,适用于处理树形结构的场景。通过透明组合模式和安全组合模式,两者各有优缺点,开发者应根据具体需求选择合适的实现方式。组合模式不仅简化了客户端的代码,还提高了代码的可扩展性和可维护性。
透明组合模式完整代码
#include <iostream>
#include <list>
using namespace std;
//抽象父类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) {}
//显示名字
void ShowName(int level)
{
for (int i = 0; i < level; ++i) { cout << " "; } //显示若干个空格用与对齐
cout << "-" << m_sname << endl;
}
virtual int Add(FileSystem* pfilesys)
{
//文件中其实是不可以增加其他文件或者目录的,所以这里直接返回-1,无奈的是父类中定义了该接口,所以子类中也必须实现该接口
return -1;
}
virtual int Remove(FileSystem* pfilesys)
{
//文件中不包含其他文件或者目录,所以这里直接返回-1
return -1;
}
private:
string m_sname; //文件名
};
//目录相关类
class Dir :public FileSystem
{
public:
//构造函数
Dir(string name) :m_sname(name) {}
//显示名字
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);
}//end for
}
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()
{
//(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);
//(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;
}
安全组合模式完整代码
#include <iostream>
#include <list>
using namespace std;
class Dir;
//抽象父类FileSystem(抽象接口)
class FileSystem
{
public:
virtual void ShowName(int level) = 0; //显示名字,参数level用于表示显示的层级,用于显示对齐
virtual ~FileSystem() {}//做父类时析构函数应该为虚函数
public:
virtual Dir* ifCompositeObj() { return nullptr; }
public:
virtual int countNumOfFiles() = 0; //统计目录下包含的文件个数
};
//文件相关类
class File :public FileSystem
{
public:
//构造函数
File(string name) :m_sname(name) {}
//显示名字
void ShowName(int level)
{
for (int i = 0; i < level; ++i) { cout << " "; } //显示若干个空格用与对齐
cout << "-" << m_sname << endl;
}
public:
virtual int countNumOfFiles()
{
return 1; //文件节点,做数量统计时按1计算
}
private:
string m_sname; //文件名
};
class Dir :public FileSystem
{
//该类内容基本没变,把Add和Remove成员函数前面的virtual修饰符去掉即可。
public:
//构造函数
Dir(string name) :m_sname(name) {}
//显示名字
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);
}//end for
}
/*virtual*/ int Add(FileSystem* pfilesys)
{
m_child.push_back(pfilesys);
return 0;
}
/*virtual*/ int Remove(FileSystem* pfilesys)
{
m_child.remove(pfilesys);
return 0;
}
public:
virtual Dir* ifCompositeObj() { return this; }
public:
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()
{
//(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");
if (pdir2->ifCompositeObj() != nullptr)
{
//是个组合对象,可以向其中增加单个对象和其它组合对象
}
if (dynamic_cast<Dir*>(pdir2) != nullptr)
{
//是个组合对象,可以向其中增加单个对象和其它组合对象
}
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);
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;
}