目录
意图
- 将对象组合成树结构以表示整个部分的层次结构。Composite 让客户可以统一处理单个对象和对象的组合。
- 递归组合
- “目录包含条目,每个条目都可以是一个目录。”
- 一对多“有一个”在“是一个”层次结构中
问题
应用程序需要操作“原始”和“复合”对象的分层集合。原始对象的处理以一种方式处理,复合对象的处理以不同的方式处理。在尝试处理它之前必须查询每个对象的“类型”是不可取的。
讨论
定义一个抽象基类(组件),它指定需要在所有原始对象和复合对象中统一执行的行为。从 Component 类中继承 Primitive 和 Composite 类。每个 Composite 对象在管理其“子对象”时仅将自身“耦合”到抽象类型 Component。
只要您有“包含组件的复合材料,每个组件都可以是复合材料”,请使用此模式。
子管理方法[例如addChild()
,removeChild()
] 通常应该在 Composite 类中定义。不幸的是,为了统一处理 Primitives 和 Composites,需要将这些方法移到抽象的 Component 类中。有关“安全”与“透明度”问题的讨论,请参阅下面的“意见”部分。
结构
包含组件的组合,每个组件都可以是一个组合。

包含菜单项的菜单,每个菜单项都可以是一个菜单。
包含小部件的行列 GUI 布局管理器,每个小部件都可以是行列 GUI 布局管理器。
包含文件的目录,每个文件都可以是一个目录。
包含元素的容器,每个元素都可以是一个容器。
例子
Composite 将对象组合成树结构,并让客户统一处理单个对象和组合。虽然这个例子是抽象的,但算术表达式是复合的。算术表达式由一个操作数、一个运算符 (+ – * /) 和另一个操作数组成。操作数可以是数字或其他算术表达式。因此,2 + 3 和 (2 + 3) + (4 * 6) 都是有效的表达式。

检查清单
- 确保您的问题是关于表示“整体”层次关系。
- 考虑启发式“包含容器的容器,每个容器都可以是一个容器”。例如,“包含组件的组件,每个组件都可以是一个组件。” 将您的领域概念划分为容器类和容器类。
- 创建一个“最小公分母”界面,使您的容器和容器可互换。它应该指定需要在所有容器对象和容器对象中统一执行的行为。
- 所有容器和容器类都声明与接口的“是”关系。
- 所有容器类都声明与接口的一对多“具有”关系。
- 容器类利用多态性委托给它们的容器对象。
- 子管理方法[例如
addChild()
,removeChild()
] 通常应该在 Composite 类中定义。不幸的是,想要统一处理 Leaf 和 Composite 对象可能需要将这些方法提升为抽象的 Component 类。有关这些“安全”与“透明度”权衡的讨论,请参见四人帮。
经验法则
- Composite 和 Decorator 具有相似的结构图,反映了两者都依赖递归组合来组织无限数量的对象的事实。
- 复合材料可以用迭代器遍历。访问者可以对组合应用操作。Composite 可以使用责任链让组件通过其父级访问全局属性。它还可以使用装饰器来覆盖部分合成的这些属性。它可以使用 Observer 将一个对象结构绑定到另一个对象结构,并使用 State 让组件随着其状态的变化而改变其行为。
- Composite 可以让您通过递归组合将较小的部分组合成 Mediator。
- 装饰器旨在让您无需子类化就可以向对象添加职责。Composite的重点不是装饰,而是表现。这些意图是不同但互补的。因此,Composite 和 Decorator 经常一起使用。
- Flyweight 通常与 Composite 结合来实现共享叶节点。
意见
Composite 模式的全部意义在于 Composite 可以被原子地处理,就像一片叶子一样。如果你想提供一个迭代器协议,很好,但我认为这超出了模式本身。这种模式的核心是客户端能够对对象执行操作,而无需知道里面有很多对象。
能够以原子方式(或透明地)处理对象的异构集合需要在 Composite 类层次结构(抽象 Component 类)的根部定义“子管理”接口。但是,这种选择会损害您的安全,因为客户端可能会尝试做一些无意义的事情,例如从叶对象中添加和删除对象。另一方面,如果你“为安全而设计”,子管理接口是在 Composite 类中声明的,你会失去透明度,因为叶子和 Composite 现在有不同的接口。
Composite 模式的 Smalltalk 实现通常在 Component 接口中没有用于管理组件的接口,而是在 Composite 接口中。C++ 实现倾向于将其放在 Component 接口中。这是一个非常有趣的事实,也是我经常思考的。我可以提供理论来解释它,但没有人确切知道为什么它是正确的。
我的 Component 类不知道 Composites 存在。它们对导航 Composite 没有任何帮助,对更改 Composite 的内容也没有任何帮助。这是因为我希望基类(及其所有派生类)在不需要复合材料的上下文中可重用。当给定一个基类指针时,如果我绝对需要知道它是否是一个组合,我会用它dynamic_cast
来解决这个问题。在那些dynamic_cast
太贵的情况下,我会使用访客。
常见的抱怨:“如果我将 Composite 接口下推到 Composite 类中,我将如何枚举(即遍历)一个复杂的结构?” 我的回答是,当我的行为适用于复合模式中呈现的层次结构时,我通常使用访问者,因此枚举不是问题 – 访问者在每种情况下都知道它正在处理的对象类型。访问者不需要每个对象都提供枚举接口。
Composite 不会强迫您将所有组件都视为复合材料。它只是告诉您将所有要“统一”处理的操作放在 Component 类中。如果 add、remove 和类似的操作不能或不能被统一对待,那么不要将它们放在 Component 基类中。顺便记住,每个模式的结构图都没有定义模式;它只是描述了我们的经验中的普遍认识。仅仅因为 Composite 的结构图显示了 Component 基类中的子管理操作,并不意味着该模式的所有实现都必须这样做。
本文来自转载,原文链接: