结构型设计模式——组合模式
树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是**组合模式(Composite Pattern)**需要解决的问。组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。**同时容器对象与抽象构件
组合模式
树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是**组合模式(Composite Pattern)**需要解决的问
题。
组合模式描述了在对待一组对象实例的时候,使用以单个对象实例相同的方式对待。组合的目的是将对象“组合”成树形结构,以表示部分-整体层次结构。通过实现组合模式,客户端可以统一对待各个对象与组合。
组合模式又称为部分-整体(Part-Whole)模式,它将对象组织到树形结构中,可以用来描述整体和部分的关系。
结构
在组合模式结构图中包含如下几个角色:
- Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
- Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
- Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
**组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。**同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
实现
为了能够更加清楚地描述出设计模式中的组合关系(不是UML中的组合关系),在AbstractTeam和ManagerTeam之间画了两条线:
- 继承关系:对节点的操作使用的是抽象类中提供的接口,以保证操作的一致性
- 聚合关系:ManagerTeam类型的节点还可以有子节点,父节点和子节点的之间的关系需要具体问题具体分析
- 子节点跟随父节点一起销毁,二者就是组合关系(UML中的组合关系)
- 子节点不跟随父节点一起销毁,二者就是聚合关系
- 上面的程序中,在父节点的析构函数中没有销毁它管理的子节点,所以在上图中标记的是聚合关系
// 抽象节点
class AbstractTeam
{
public:
//设置当前船队的名字
AbstractTeam(string name) :m_name(name) {}
// 设置父节点
void setParent(AbstractTeam* node)
{
m_parent = node;
}
//得到当前船队节点的父节点
AbstractTeam* getParent()
{
return m_parent;
}
//获得当前船队的名字
string getName()
{
return m_name;
}
virtual bool hasChild()
{
return false;
}
//给当前番队添加一个子船队节点
virtual void add(AbstractTeam* node) {}
//跟当前番队删除一个子船队节点
virtual void remove(AbstractTeam* node) {}
//当前番队和敌人战斗
virtual void fight() = 0;
//显示当前番队的信息
virtual void display() = 0;
virtual ~AbstractTeam() {}
protected:
string m_name;
AbstractTeam* m_parent = nullptr;
};
// 叶子节点的小队
class LeafTeam : public AbstractTeam
{
public:
using AbstractTeam::AbstractTeam;
void fight() override
{
cout << m_parent->getName() + m_name + "与黑胡子的船员进行近距离肉搏战..." << endl;
}
void display() override
{
cout << "我是" << m_parent->getName() << "下属的" << m_name << endl;
}
~LeafTeam()
{
cout << "我是" << m_parent->getName() << "下属的" << m_name
<< ", 战斗已经结束, 拜拜..." << endl;
}
};
// 管理者节点
class ManagerTeam : public AbstractTeam
{
public:
using AbstractTeam::AbstractTeam;
void fight() override
{
cout << m_name + "和黑胡子的恶魔果实能力者战斗!!!" << endl;
}
//把当前番队的子节点存储到list中
void add(AbstractTeam* node) override
{
node->setParent(this);
m_children.push_back(node);
}
//把某一个子节点从当前番队的list中删除
void remove(AbstractTeam* node) override
{
node->setParent(nullptr);
m_children.remove(node);
}
bool hasChild()
{
return true;
}
list<AbstractTeam*> getChildren()
{
return m_children;
}
//遍历这个list容器中的节点
void display()
{
string info = string();
for (const auto item : m_children)
{
if (item == m_children.back())
{
info += item->getName();
}
else
{
// 优先级: + > +=
info += item->getName() + ", ";
}
}
cout << m_name + "的船队是【" << info << "】" << endl;
}
~ManagerTeam()
{
cout << "我是【" << m_name << "】战斗结束, 拜拜..." << endl;
}
private:
//容器内存储的就是它的子节点对象
list<AbstractTeam*> m_children;
};
// 内存释放
void gameover(AbstractTeam* root)
{
if (root == nullptr)
{
return;
}
if (root && root->hasChild())
{
ManagerTeam* team = dynamic_cast<ManagerTeam*>(root);
list<AbstractTeam*> children = team->getChildren();
for (const auto item : children)
{
gameover(item);
}
}
delete root;
}
// 和黑胡子战斗
void fighting()
{
vector<string> nameList = {
"俊美海贼团", "巴托俱乐部", "八宝水军", "艾迪欧海贼团",
"咚塔塔海贼团", "巨兵海贼团", "约塔玛利亚大船团"
};
// 根节点
ManagerTeam* root = new ManagerTeam("草帽海贼团");
for (int i = 0; i < nameList.size(); ++i)
{
ManagerTeam* child = new ManagerTeam(nameList.at(i));
root->add(child);
if (i == nameList.size() - 1)
{
// 给最后一个番队添加子船队
for (int j = 0; j < 9; ++j)
{
LeafTeam* leaf = new LeafTeam("第" + to_string(j + 1) + "番队");
child->add(leaf);
leaf->fight();
leaf->display();
}
child->fight();
child->display();
}
}
root->fight();
root->display();
cout << "====================================" << endl;
gameover(root);
}
int main()
{
fighting();
return 0;
}
特点
要优点
- 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
- 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
- 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
主要缺点
- 在增加新构件时很难对容器中的构件类型进行限制。
- 有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。
适用环境
- 如果你需要实现树状对象结构, 可以使用组合模式。组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得你可以构建树状嵌套递归对象结构。
- 如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)