组合模式
一、动机
1.软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性和扩展性弊端。
2.如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器
二、介绍
意图: 将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
主要解决: 它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
如何解决: 树枝和叶子实现统一接口,树枝内部组合该接口。
关键代码: 树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。
优点: 1、高层模块调用简单。 2、节点自由增加。
缺点: 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
使用场景: 部分、整体场景,如树形菜单,文件、文件夹的管理。
注意事项: 定义时为具体类。
三、结构
四、要点总结
1.组合模式才用树形结构来实现普遍存在的对象容器,从而将一对多的关系转化为一对一的关系,使得客户代码可以一致的附庸处理对象和对象容器,无需关系处理的是单个的对象还是组合的对象容器
2.将客户代码与复杂的对象容器结构解耦是组合模式和核心思想,解耦之后客户代码将与纯粹的抽象接口(非对象容器的内部实现)发生依赖,从而更能应对变化。
3.组合模式在具体实现中可以让父对象的子对象反向追溯,如果父对象有频繁的遍历需求,可以使用缓存技巧来改善效率
五、代码展示
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. process current node //2. process leaf nodes 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); process(root); process(leaf2); process(treeNode3); }