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