组合模式是一种结构型设计模式,它允许我们将对象组织成树状结构,并以递归的方式处理它们。该模式通过将单个对象和组合对象统一对待,使得客户端可以以一致的方式处理对象集合。
组合模式中有两种角色:组合和组件。组件就是叶子节点,它们没有子组件。组合是包含其他组件的容器节点,可以包含子组合或者叶子节点。
组合模式的核心思想是将对象组织成树状结构,其中单个对象和组合对象都实现了相同的接口或者继承相同的抽象类,这样可以使客户端以递归的方式处理整个对象树,不需要关心处理的节点是叶子节点还是组合节点。
举例:
我们现在要实现一个文件系统,文件系统下有多个目录或者文件,每个目录都会有多个目录和文件。
c
#pragma once
#include <iostream>
#include <memory>
#include <vector>
// 抽象类
class IFileSystem
{
public:
virtual ~IFileSystem() {}
virtual void Display() = 0;
};
// 文件,也就是叶子节点
class File
: public IFileSystem
{
public:
File(const std::string& _fileName)
: fileName_(_fileName)
{}
virtual void Display() override
{
std::cout << fileName_ << std::endl;
}
private:
std::string fileName_;
};
// 目录,也就是组合节点
class Directory
: public IFileSystem
{
public:
Directory(const std::string _directoryName)
: directoryName_(_directoryName)
{}
void AddComponment(std::shared_ptr<IFileSystem> _componment)
{
components_.emplace_back(_componment);
}
virtual void Display() override
{
std::cout << directoryName_ << std::endl;
// 递归处理子节点
for (auto it : components_)
it->Display();
}
private:
// 目录名字
std::string directoryName_;
// 子节点
std::vector<std::shared_ptr<IFileSystem>> components_;
};
在这个示例中,FileSystemComponent 是一个抽象基类,定义了所有文件系统组件需要实现的操作。File 是一个叶子节点类,代表文件对象,它实现了 Display 方法来显示文件名。Directory 是一个组合节点类,代表目录对象,它除了实现 Display 方法外,还可以通过 AddComponent 方法添加子节点。
不同的是在组合节点目录 中,我们有一个存放子节点的容器components_,它存放的就是该目录下的所有组合节点或者叶子节点,以便可以递归处理它们。
测试:
c
void TestComponment()
{
// 创建文件系统
std::shared_ptr<Directory> root = std::make_shared<Directory>("Root");
// 创建两个目录
std::shared_ptr<Directory> sub1 = std::make_shared<Directory>("目录1");
std::shared_ptr<Directory> sub2 = std::make_shared<Directory>("目录2");
// 创建三个文件
std::shared_ptr<File> file1 = std::make_shared<File>("文件1");
std::shared_ptr<File> file2 = std::make_shared<File>("文件2");
std::shared_ptr<File> file3 = std::make_shared<File>("文件3");
// 创建树结构
root->AddComponment(sub1);
root->AddComponment(sub2);
// 目录1下面有一个文件
sub1->AddComponment(file1);
// 目录2下面有两个文件
sub2->AddComponment(file2);
sub2->AddComponment(file3);
root->Display();
}
测试代码中,我们创建了一个根节点 root,它下面有两个目录,其中目录1 里有一个文件文件1 ,目录2 有两个文件文件2 和文件3。
输出:
c
Root
目录1
文件1
目录2
文件2
文件3
我们可以看到系统依次输出目录和文件名,它们的树状结构是这样的:
组合模式遵循以下设计原则:
1、单一职责原则 (Single Responsibility Principle):文件类只负责输出自己的文件名,目录类负责输出自己的目录名和其子节点的所有名称,即每个类应该只有一个责任。
2、开闭原则 (Open-Closed Principle):再给这个文件系统中添加一个组合节点或者子节点我们只需要添加像文件或者目录这样的类即可,即系统应该对扩展开放,对修改关闭。
3、接口隔离原则 (Interface Segregation Principle):客户端没有依赖抽象接口,即客户端不应该依赖于它不需要使用的接口。
4、依赖倒置原则 (Dependency Inversion Principle):文件和目录都依赖抽象接口,即高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
优点:
1、简化客户端代码:客户端可以一致地使用单个对象或组合对象,而不需要区分它们之间的差异。
2、灵活性和可扩展性:可以很容易地增加新的组件,而不会影响现有的组件。
3、对象层次结构的一致性:组合模式让单个对象和组合对象具有相似的操作,使得整个对象层次结构更加统一。
缺点:
1、可能会加重系统的复杂性:在一些场景下,使用组合模式可能会导致系统变得更加复杂,特别是在对象层次结构比较庞大时。
2、可能会降低运行效率:由于组合模式需要通过递归来操作对象层次结构,可能会导致一些性能损耗。