概述
- 数据结构模式
- 常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用
- 将这些特定数据模式封装在内部,对外提供统一的接口,来实现与特定数据结构无关的访问
- 典型模式:Composite, Iterator, Chain of Resposibility
- 属于结构模式(按目的划分)
- 动机:软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端
- 如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
- 定义:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)
- 采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化成“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器
- 将“客户代码与复杂的对象容器结构”结构是Composite的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖,从而更能“应对变化”
- Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率
- 如何计算一个复杂订单的总价
- 采用通用接口与产品和盒子进行交互,在接口中声明一个计算总价的方法
- 对于产品,直接返回价格,对于盒子,遍历其中所有项目,询问每个项目的价格,然后返回盒子总价格
- 简单叶子节点和复杂容器实现共享接口,容器中可包含叶子节点和其他容器,从而可构建树状嵌套递归对象
- 所有元素共用一个接口,客户端不必在意其使用的对象的具体类
场景
- 部分、整体场景,如树形菜单、文件、文件夹的管理
示例1
- 34-36:多态调用,将内部数据结构封装
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <list> 3 #include <string> 4 #include <algorithm> 5 6 using namespace std; 7 8 class Component 9 { 10 public: 11 virtual void process() = 0; 12 virtual ~Component(){} 13 }; 14 15 //树节点 16 class Composite : public Component{ 17 18 string name; 19 list<Component*> elements; 20 public: 21 Composite(const string & s) : name(s) {} 22 23 void add(Component* element) { 24 elements.push_back(element); 25 } 26 void remove(Component* element){ 27 elements.remove(element); 28 } 29 30 void process(){ 31 32 //1. process current node 33 34 //2. process leaf nodes 35 for (auto &e : elements) 36 e->process(); //多态调用 37 38 } 39 }; 40 41 //叶子节点 42 class Leaf : public Component{ 43 string name; 44 public: 45 Leaf(string s) : name(s) {} 46 47 void process(){ 48 //process current node 49 } 50 }; 51 52 void Invoke(Component & c){ 53 //... 54 c.process(); 55 //... 56 } 57 58 int main() 59 { 60 61 Composite root("root"); 62 Composite treeNode1("treeNode1"); 63 Composite treeNode2("treeNode2"); 64 Composite treeNode3("treeNode3"); 65 Composite treeNode4("treeNode4"); 66 Leaf leat1("left1"); 67 Leaf leat2("left2"); 68 69 root.add(&treeNode1); 70 treeNode1.add(&treeNode2); 71 treeNode2.add(&leaf1); 72 73 root.add(&treeNode3); 74 treeNode3.add(&treeNode4); 75 treeNode4.add(&leaf2); 76 77 process(root); 78 process(leaf2); 79 process(treeNode3); 80 81 }
示例2
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <algorithm> 2 #include <iostream> 3 #include <list> 4 #include <string> 5 #include <cstdlib> 6 using namespace std; 7 /** 8 * The base Component class declares common operations for both simple and 9 * complex objects of a composition. 10 */ 11 class Component { 12 /** 13 * @var Component 14 */ 15 protected: 16 Component *parent_; 17 /** 18 * Optionally, the base Component can declare an interface for setting and 19 * accessing a parent of the component in a tree structure. It can also 20 * provide some default implementation for these methods. 21 */ 22 public: 23 virtual ~Component() {} 24 void SetParent(Component *parent) { 25 this->parent_ = parent; 26 } 27 Component *GetParent() const { 28 return this->parent_; 29 } 30 /** 31 * In some cases, it would be beneficial to define the child-management 32 * operations right in the base Component class. This way, you won't need to 33 * expose any concrete component classes to the client code, even during the 34 * object tree assembly. The downside is that these methods will be empty for 35 * the leaf-level components. 36 */ 37 virtual void Add(Component *component) {} 38 virtual void Remove(Component *component) {} 39 /** 40 * You can provide a method that lets the client code figure out whether a 41 * component can bear children. 42 */ 43 virtual bool IsComposite() const { 44 return false; 45 } 46 /** 47 * The base Component may implement some default behavior or leave it to 48 * concrete classes (by declaring the method containing the behavior as 49 * "abstract"). 50 */ 51 virtual std::string Operation() const = 0; 52 }; 53 /** 54 * The Leaf class represents the end objects of a composition. A leaf can't have 55 * any children. 56 * 57 * Usually, it's the Leaf objects that do the actual work, whereas Composite 58 * objects only delegate to their sub-components. 59 */ 60 class Leaf : public Component { 61 public: 62 std::string Operation() const override { 63 return "Leaf"; 64 } 65 }; 66 /** 67 * The Composite class represents the complex components that may have children. 68 * Usually, the Composite objects delegate the actual work to their children and 69 * then "sum-up" the result. 70 */ 71 class Composite : public Component { 72 /** 73 * @var SplObjectStorage 74 */ 75 protected: 76 std::list<Component *> children_; 77 78 public: 79 /** 80 * A composite object can add or remove other components (both simple or 81 * complex) to or from its child list. 82 */ 83 void Add(Component *component) override { 84 this->children_.push_back(component); 85 component->SetParent(this); 86 } 87 /** 88 * Have in mind that this method removes the pointer to the list but doesn't 89 * frees the 90 * memory, you should do it manually or better use smart pointers. 91 */ 92 void Remove(Component *component) override { 93 children_.remove(component); 94 component->SetParent(nullptr); 95 } 96 bool IsComposite() const override { 97 return true; 98 } 99 /** 100 * The Composite executes its primary logic in a particular way. It traverses 101 * recursively through all its children, collecting and summing their results. 102 * Since the composite's children pass these calls to their children and so 103 * forth, the whole object tree is traversed as a result. 104 */ 105 std::string Operation() const override { 106 std::string result; 107 for (const Component *c : children_) { 108 if (c == children_.back()) { 109 result += c->Operation(); 110 } else { 111 result += c->Operation() + "+"; 112 } 113 } 114 return "Branch(" + result + ")"; 115 } 116 }; 117 /** 118 * The client code works with all of the components via the base interface. 119 */ 120 void ClientCode(Component *component) { 121 // ... 122 std::cout << "RESULT: " << component->Operation(); 123 // ... 124 } 125 126 /** 127 * Thanks to the fact that the child-management operations are declared in the 128 * base Component class, the client code can work with any component, simple or 129 * complex, without depending on their concrete classes. 130 */ 131 void ClientCode2(Component *component1, Component *component2) { 132 // ... 133 if (component1->IsComposite()) { 134 component1->Add(component2); 135 } 136 std::cout << "RESULT: " << component1->Operation(); 137 // ... 138 } 139 140 /** 141 * This way the client code can support the simple leaf components... 142 */ 143 144 int main() { 145 Component *simple = new Leaf; 146 std::cout << "Client: I've got a simple component: "; 147 ClientCode(simple); 148 std::cout << " "; 149 /** 150 * ...as well as the complex composites. 151 */ 152 153 Component *tree = new Composite; 154 Component *branch1 = new Composite; 155 156 Component *leaf_1 = new Leaf; 157 Component *leaf_2 = new Leaf; 158 Component *leaf_3 = new Leaf; 159 branch1->Add(leaf_1); 160 branch1->Add(leaf_2); 161 Component *branch2 = new Composite; 162 branch2->Add(leaf_3); 163 tree->Add(branch1); 164 tree->Add(branch2); 165 std::cout << "Client: Now I've got a composite tree: "; 166 ClientCode(tree); 167 std::cout << " "; 168 169 std::cout << "Client: I don't need to check the components classes even when managing the tree: "; 170 ClientCode2(tree, simple); 171 std::cout << " "; 172 173 delete simple; 174 delete tree; 175 delete branch1; 176 delete branch2; 177 delete leaf_1; 178 delete leaf_2; 179 delete leaf_3; 180 181 return 0; 182 }
联系
- 桥接、状态、策略、适配器模式的接口非常类似,都基于组合模式
- 创建复杂组合树时使用生成器模式,使其构造步骤以递归的方式进行
- 通常与责任链模式结合使用,叶组件接收到请求后,可一直传递到对象树的底部
- 可用迭代器模式遍历组合树
- 可用访问者模式对组合树进行操作
- 可用享元模式实现组合树的共享叶节点,以节省内存
- 和装饰模式的结构图很像,都依赖递归组合来组织无限数目的对象,不同在于,装饰模式只有一个子组件,且为封装对象添加了额外的职责,而组合仅对子节点的结果进行了求和
- 可使用原型模式复制复杂结构,而非从零开始构造