简单介绍
组合模式是一种结构型设计模式, 只有在可以将对象拆分为【树状结构】的情况下使用。并且像使用独立对象一样使用它们。
常用于表示与图形打交道的用户界面组件或代码的层次结构。
基础理解
Q:为什么要用组合模式 ?
A:在一个树状的结构中,叶子节点与父节点的距离会很远,使用循环语句一个一个遍历会大大增加复杂度。所以需要使用一个通用接口将叶子节点与父节点实现交互。
Q:如何设计组合模式 ?
A:对于一个字节点,直接返回你需要的东西,对于父节点,则会遍历其所有的子节点,最后 " 汇总 " 结果给其父节点
优点:无需了解构成树状结构的对象的具体类,无论复杂简单。直接调用接口进行处理,对象会沿着树结构递归下去
识别方法: 组合模式会将同一抽象或接口类型的实例放入树状结构。
UML
实现方式
- 首先确保你自己 的模型能被拆分为树状结构。并且尝试分解为简单元素和容器(必须能够同时包含简单元素和其他容器)
- 声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。
- 创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。
- 创建一个容器类表示复杂元素,创建一个数组成员变量来存储对于其子元素的引用。需要能同时保存叶节点和容器(父类)
- 在容器中定义添加和删除子元素的方法。
#include <algorithm>
#include <iostream>
#include <list>
#include <string>
/**
* 基础组件类声明了复合对象和简单对象的共同操作。
*/
class Component
{
/**
* @var Component
*/
protected:
Component *parent_;
/**
* 可选地,基础组件可以声明一个接口,用于设置和访问组件在树结构中的父级。它还可以为这些方法提供一些默认实现。
*/
public:
virtual ~Component() {}
void SetParent(Component *parent)
{
this->parent_ = parent;
}
Component *GetParent() const
{
return this->parent_;
}
/**
* 在某些情况下,在基础组件类中定义管理子组件的操作会很有益。这样,即使在对象树组装期间,您也不需要向客户端代码公开任何具体的组件类。缺点是对于叶级组件,这些方法将为空。
*/
virtual void Add(Component *component) {}
virtual void Remove(Component *component) {}
/**
* 您可以提供一个方法,让客户端代码确定组件是否可以拥有子组件。
*/
virtual bool IsComposite() const
{
return false;
}
/**
* 基础组件可以实现一些默认行为,或者将其委托给具体类(通过将包含行为的方法声明为“abstract”)。
*/
virtual std::string Operation() const = 0;
};
/**
* 叶级类表示组合的末端对象。叶级对象不能有任何子节点。
*
* 通常情况下,真正的工作是由叶级对象完成的,而复合对象只是将任务委派给它们的子组件。
*/
class Leaf : public Component
{
public:
std::string Operation() const override
{
return "Leaf";
}
};
/**
* Composite 类代表可能具有子组件的复杂组件。
* 通常,Composite 对象将实际工作委托给它们的子组件,
* 然后“汇总”结果。
*/
class Composite : public Component
{
protected:
std::list<Component *> children_;
public:
/**
* Composite 对象可以向其子组件列表中添加或删除其他组件(简单或复杂)。
*/
void Add(Component *component) override
{
this->children_.push_back(component);
component->SetParent(this);
}
/**
* 注意,此方法只是从列表中移除指针,但不释放内存,
* 您应该手动释放内存,或者最好使用智能指针。
* 我使用手动,简单理解
*/
void Remove(Component *component) override
{
children_.remove(component);
component->SetParent(nullptr);
delete component;
}
bool IsComposite() const override
{
return true;
}
/**
* Composite 在特定方式下执行其主要逻辑。
* 它递归遍历所有子组件,收集和汇总它们的结果。
* 由于 Composite 的子组件会将这些调用传递给它们的子组件,以此类推,
* 整个对象树将被遍历并返回结果。
*/
std::string Operation() const override
{
std::string result;
for (const Component *c : children_)
{
if (c == children_.back())
{
result += c->Operation();
}
else
{
result += c->Operation() + "+";
}
}
return "Branch(" + result + ")";
}
};
/**
* 客户端代码通过基础接口与所有组件一起工作。
*/
void ClientCode(Component *component)
{
// ...
std::cout << "RESULT: " << component->Operation(); //同一接口
// ...
}
/**
* 由于子组件管理操作在基类 Component 中声明,
* 客户端代码可以处理任何组件,无论是简单还是复杂,而不依赖于具体的类。
*/
void ClientCode2(Component *component1, Component *component2)
{
// ...
if (component1->IsComposite())
{
component1->Add(component2);
}
std::cout << "RESULT: " << component1->Operation();
// ...
}
/**
* 这样,客户端代码可以支持简单的叶子组件...
*/
int main()
{
Leaf *leaf = new Leaf;
std::cout << "Client: 我有一个简单的组件:\n";
ClientCode(leaf);
std::cout << "\n\n";
/**
* ...以及复杂的 Composite 组件。
*/
Composite *tree = new Composite;
Composite *branch1 = new Composite;
Composite *branch2 = new Composite;
Leaf *leaf1 = new Leaf;
Leaf *leaf2 = new Leaf;
Leaf *leaf3 = new Leaf;
Leaf *leaf4 = new Leaf;
branch1->Add(leaf1);
branch1->Add(leaf2);
branch2->Add(leaf3);
branch2->Add(leaf4);
tree->Add(branch1);
tree->Add(branch2);
std::cout << "Client: 现在我有一个复合树结构:\n";
ClientCode(tree);
std::cout << "\n\n";
std::cout << "Client: 即使在管理树结构时,我不需要检查组件的具体类:\n";
ClientCode2(tree, leaf);
std::cout << "\n";
delete leaf;
delete tree;
delete branch1;
delete branch2;
delete leaf1;
delete leaf2;
delete leaf3;
delete leaf4;
system("pause");
return 0;
}
应用场景
- 实现树状对象结构, 可以使用组合模式。
大致可以理解为,两种共享公共接口的基本元素: 简单叶节点和复杂容器(包含其他容器)
- 客户端代码以相同方式处理简单和复杂元素
同一接口就是相同方式,但可以使用组合模式同时处理简单和复杂元素。
与其他模式的关系
-
桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式:将转换接口的工作给适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。
(模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。) -
你可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式(特定的方式)运行。
-
责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。
-
你可以使用 迭代器模式 来遍历组合树。
(对不同结构使用不同的迭代方式,有点类似策略模式:使用对应的算法解决问题) -
你可以使用访问者模式对整个组合树执行操作。
-
你可以使用享元模式实现组合树的共享叶节点以节省内存。
-
组合和装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。
- 装饰类似于组合,对一个产品套上一层层装饰,最终的结构十分相似。 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。
- 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。
- 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。