作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
组合模式是一种结构型的软件设计模式,将对象组合成树形结构,以凸显“部分-整体”的层次结构,使客户端对单个对象和组合对象的操作具备一致性。
组合模式和桥接模式都应用了组合的思想,不同之处在于:桥接模式侧重于同级别间的组合,如多个属性的组合,避免了类爆炸;组合模式侧重于部分和整体的组合,避免了单对象和组合对象的区别对待,那样会增加程序复杂度。
组合模式的优点:
组合模式的缺点:
组合模式一般分为透明式组合模式和安全式组合模式。
1)透明式组合模式:将公共接口封装到抽象节点中,所有节点具备一致行为。
2)安全式组合模式:各层次差异较大,使用不同操作时建议采用该模式。
客户端即Main主函数,采用透明式,无需分辨节点是叶子还是容器,只需使用抽象节点。
场景描述:我要设计一个文件系统,在文件夹中可以添加删除文件或者文件夹。
//File.h
/****************************************************/
#pragma once
#include
#include
using namespace std;
// 抽象类-节点
class Node
{
public:
// 构造函数
explicit Node(string name) :m_name(name) {};
// 析构函数
virtual ~Node() {};
// 添加
virtual void add(Node *node) {};
// 删除
virtual void remove(Node *node) {};
// 显示
virtual void show(int space) {
for (int i = 0; i < space; i++) {
cout << " ";
}
cout << m_name << endl;
}
protected:
string m_name; // 名字
};
// 具体类-Word文件
class WordFile :public Node
{
public:
// 构造函数
explicit WordFile(string name) :Node(name) {};
// 析构函数
virtual ~WordFile() {};
};
// 具体类-文件夹
class Folder :public Node
{
public:
// 构造函数
explicit Folder(string name) :Node(name) {};
// 析构函数
virtual ~Folder() {
nodeList.clear();
}
// 添加
virtual void add(Node *node) {
nodeList.emplace_back(node);
}
// 删除
virtual void remove(Node *node) {
nodeList.remove(node);
}
// 显示
virtual void show(int space) {
Node::show(space);
space++;
for (auto node : nodeList) {
node->show(space);
}
}
private:
list nodeList; // 节点列表
};
//main.cpp
/****************************************************/
#include
#include
#include "File.h"
using namespace std;
int main()
{
Node *f0 = new Folder("我的文件夹");
// 文件夹1中放入Word2和Word3,并将文件夹1放入我的文件夹
Node *f1 = new Folder("文件夹1");
Node *w2 = new WordFile("Word2");
Node *w3 = new WordFile("Word3");
f1->add(w2);
f1->add(w3);
f0->add(f1);
// 将Word1放入我的文件夹
Node *w1 = new WordFile("Word1");
f0->add(w1);
// 显示我的文件夹中的内容
f0->show(0);
// 删除文件夹1中的Word2文件,再次显示我的文件夹中的内容
f1->remove(w2);
f0->show(0);
// 删除指针并置空
delete f0, f1, w1, w2, w3;
f0 = nullptr;
f1 = nullptr;
w1 = nullptr;
w2 = nullptr;
w3 = nullptr;
return 0;
}
程序结果如下。
在上述示例中,我采用的都是抽象节点,因为抽象类中涵盖了几乎所有的行为。对外界(客户端)而言,叶子和容器的行为没有区别,它们的区别是不可见的也是透明的。但同样,这样的设计一定意义上也是不安全的,因为叶子不可能和容器有完全一致的行为,就像上文中文件夹的增删功能,文件是没有的,如果对文件进行文件夹的相关独特操作,要做相关的异常判断。
该模式是组合模式中常用的,虽然它违背了设计模式中的接口隔离原则(只提供需要的接口,不需要的接口要屏蔽)。
客户端即Main主函数,采用安全式,抽象类中只规定基础操作,而叶子和容器各自独有的操作将放在自身中完成,因此客户端在调用时无法直接使用抽象节点。
场景描述:我要设计一个文件系统,在文件夹中可以添加删除文件或者文件夹。
//File.h
/****************************************************/
#pragma once
#include
#include
using namespace std;
// 抽象类-节点
class Node
{
public:
// 构造函数
explicit Node(string name) :m_name(name) {};
// 析构函数
virtual ~Node() {};
// 显示
virtual void show(int space) = 0;
protected:
string m_name; // 名字
};
// 具体类-Word文件
class WordFile :public Node
{
public:
// 构造函数
explicit WordFile(string name) :Node(name) {};
// 析构函数
virtual ~WordFile() {};
// 显示
virtual void show(int space) {
for (int i = 0; i < space; i++) {
cout << " ";
}
cout << m_name << endl;
}
};
// 具体类-文件夹
class Folder :public Node
{
public:
// 构造函数
explicit Folder(string name) :Node(name) {};
// 析构函数
virtual ~Folder() {
nodeList.clear();
}
// 添加
void add(Node *node) {
nodeList.emplace_back(node);
}
// 删除
void remove(Node *node) {
nodeList.remove(node);
}
// 显示
virtual void show(int space) {
for (int i = 0; i < space; i++) {
cout << " ";
}
cout << m_name << endl;
space++;
for (auto node : nodeList) {
node->show(space);
}
}
private:
list nodeList; // 节点列表
};
//main.cpp
/****************************************************/
#include
#include
#include "File.h"
using namespace std;
int main()
{
Folder *f0 = new Folder("我的文件夹");
// 文件夹1中放入Word2和Word3,并将文件夹1放入我的文件夹
Folder *f1 = new Folder("文件夹1");
WordFile *w2 = new WordFile("Word2");
WordFile *w3 = new WordFile("Word3");
f1->add(w2);
f1->add(w3);
f0->add(f1);
// 将Word1放入我的文件夹
WordFile *w1 = new WordFile("Word1");
f0->add(w1);
// 显示我的文件夹中的内容
f0->show(0);
// 删除文件夹1中的Word2文件,再次显示我的文件夹中的内容
f1->remove(w2);
f0->show(0);
// 删除指针并置空
delete f0, f1, w1, w2, w3;
f0 = nullptr;
f1 = nullptr;
w1 = nullptr;
w2 = nullptr;
w3 = nullptr;
return 0;
}
程序结果如下。
在上述示例中,客户端无法直接使用抽象节点了,因为抽象节点中没有add和remove的操作,但这样更安全了,叶子和容器不会调用对方的功能进而触发异常。
注意:该模式违背了依赖倒置原则(程序设计要依赖于抽象接口,不要依赖于具体实现)。
我尽可能用较通俗的话语和直观的代码例程,来表述我对组合模式的理解,或许有考虑不周到的地方,如果你有不同看法欢迎评论区交流!希望我举的例子能帮助你更好地理解组合模式。
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!