属于“数据结构”模式,数据结构模式还包括迭代器、职责链设计模式。
动机
软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
代码
组合设计模式
好处:
- 将内部数据结构的访问封装在类内部,而不需要在外部判断
- 组合设计模式其实就是对树形结构的一种处理方式,只不过在访问时,采用多态的方式把对树访问的放在了树形结构的内部,而不是要把这个数据结构暴露给外界
#include <iostream>#include <list>#include <string>#include <algorithm>using namespace std;class Component{public:virtual void process() = 0;virtual ~Component(){}};//树节点class Composite : public Component{string name;list<Component*> elements;public:Composite(const string & s) : name(s) {}//添加子节点void add(Component* element) {elements.push_back(element);}//移除子节点void remove(Component* element){elements.remove(element);}//处理//好处:把内部数据结构的访问封装进来了,而不需要在外部判断区分void process(){//1. 处理当前节点//...//2. 处理叶子节点for (auto &e : elements)e->process(); //多态调用}};//叶子节点class Leaf : public Component{string name;public:Leaf(string s) : name(s) {}void process(){//process current node}};void Invoke(Component & c){//...c.process();//...}int main(){Composite root("root"); //根节点Composite treeNode1("treeNode1");Composite treeNode2("treeNode2");Composite treeNode3("treeNode3");Composite treeNode4("treeNode4");Leaf leat1("left1");Leaf leat2("left2");root.add(&treeNode1);treeNode1.add(&treeNode2);treeNode2.add(&leaf1);root.add(&treeNode3);treeNode3.add(&treeNode4);treeNode4.add(&leaf2);Invoke(root); //处理根节点Invoke(leaf2); //处理叶子节点Invoke(treeNode3); //处理树节点}
普通代码
如果没有用组合设计模式,那会怎么做?会带来什么问题?
#include <iostream>#include <list>#include <string>#include <algorithm>using namespace std;class Component{public:virtual void process() = 0;virtual ~Component(){}};//树节点class Composite : public Component{string name;list<Component*> elements;public:Composite(const string & s) : name(s) {}//添加子节点void add(Component* element) {elements.push_back(element);}//移除子节点void remove(Component* element){elements.remove(element);}//处理void process(){//处理当前节点}};//叶子节点class Leaf : public Component{string name;public:Leaf(string s) : name(s) {}void process(){//process current node}};void Invoke(Component & c){//判断c是Composite树节点,还是Leaf叶子节点呢?//1. 如果是树节点//取中树里面的子元素,继续处理(一对多的关系)//2. 如果是叶子节点//处理这个叶子节点(一对一的关系)}//很麻烦,而且暴露了内部的数据结构//可以通过组合设计模式,用多态的递归调用,可以将一对多,转变为一对一的操作,使问题变得简单
模式定义
将对象组合成树形结构以表示“部分-整体”的层次结构。
Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。——《设计模式》GoF
对单个对象和组合对象处理具有一致性:
Invoke(root); //处理根节点Invoke(leaf2); //处理叶子节点Invoke(treeNode3); //处理树节点
结构

最核心的一点:用多态的调用方式,实现在树形结构中,对非叶子节点与叶子节点做一致性的处理。
【有争议的一点】Add、Remove、GetChild这三个函数是放在Component父类中,还是放在Composite子类中。
其实都有不完善的地方。相对来讲,放在子类中是符合逻辑的。否则,在C++的机制中,Leaf::Add、Leaf::Remove是很尴尬的。
一、放在父类Component中
其实对于Leaf节点就很尴尬,你既然是叶子节点,那你还有Add、Remove、GetChild行为
- 有的朋友说那实现一个空的,但还是不舒服,你能够看到这些行为,而且你能调用,你Add了一个东西,但是你啥也不干
- 当然又有朋友说,你throw一个异常呢?一个异常也不合适,父类有虚函数,子类实现虚函数的时候直接丢异常,这违背了is-a关系准则(子类继承自父类,父类定义的接口,应该是能实现的,现在你仍异常,表明你不支持父类的接口)
二、放在子类Composite中
但有又一个缺点。到时候添加child时,leaf没有添加能力,只有composite有添加能力。那你这时又要去判断类型了
要点总结
要点一
Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致性(复用)处理对象和对象容器,无需关系处理的是单个的对象,还是组合的对象容器。
要点二
将“客户代码与复杂的对象容器结构”解耦是Composite的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖,从而更能“应对变化”
要点三
Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率(树访问算法方面的技巧)。
