4.3-Composite-组合-结构型模式
目的
- 将对象组合成树形结构以表示“部分–整体”的层次结构。
- Composite 使得用户对单个对象和组合对象的使用具有一致性。
示例
例如 GUI 框架允许用户可以组合多个简单组件以递归形成一些较大的组件。这有 2 种实现方式:
- 使用图元类和容器类:使用这些类的代码必须区别对待图元对象与容器对象,而实际上大多数情况下用户认为它们是一样的。
- 使用 Composite 模式递归构建
Composite 模式的关键是一个抽象类,它既可以代表图元,又可以代表图元的容器。其内部提供图元和图元容器所需的所有接口
- 它的图元子类(树的叶结点,Line,Text 等)无需实现容器相关的操作
- 聚合类 Picture 定义了一个 Graphic 的集合,它的 Draw 递归调用子部件的 Draw 操作。由于 Picture 接口与 Graphic 接口是一致的,因此 Picture 对象可以递归地组合其他 Picture 对象。

适用性
- 你想表示对象的部分–整体层次结构。
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
结构
组合结构如下:
继承结构如下:

参与者
- Component:
- 提供 Leaf 所需的操作,提供用于访问管理 Component 子组件所需的操作
- 通常需要,实现所有类共有接口的默认行为
- 如果需要,在 Component 类中提供访问父组件的接口,Leaf 和 Composite 按需实现这个接口。
- 父部件引用可以简化结构的上移和组件的删除
- Composite(Picture)
- 定义有子部件的那些部件的行为。
- 存储子部件。
- 在 Component 接口中实现与子部件有关的操作。
- Leaf:定义图元对象的行为
- Client:通过 Component 接口操纵组合部件的对象
用户使用 Component 类接口与组合结构中的对象进行交互。
- 如果接收者是一个叶结点,则直接处理请求。
- 如果接收者是 Composite,它通常将请求发送给它的子部件,在转发请求之前和/或之后可能执行一些辅助操作。
优缺点
优点:
- 简化客户代码:客户可以一致地使用组合结构和单个对象。通常用户不知道(也不关心)处理的是一个叶结点还是一个组合组件。
- 使得更容易增加新类型的组件:新定义的 Composite 或 Leaf 子类自动地与已有的结构和客户代码一起工作
缺点:
- 使你的设计变得更加一般化:容易增加新组件会导致很难限制组合中的组件。
- 有时你希望一个组合只能有某些特定的组件。使用 Composite 时,你不能依赖类型系统施加这些约束,而必须在运行时进行检查
实现
父组件引用
父组件引用需要确保一个不变式:假设组件 B 在组件 A 的子组件集合中,则 B 的父组件引用必须指向 A
实现方法:仅当在一个组合中增加或删除一个组件时,才改变这个组件的父部件
- 在 Composite 类的 Add 和 Remove 操作中实现这种方法,那么所有的子类都可以继承这一方法,并且将自动维护这一不变式。
共享组件
- 好处:减少存储占用等
- 实现方法:一个组件指向多个父组件
- 缺点:当一个请求在结构中向上传递时,这种方法会导致多义性。如果子部件可以将一些状态(或是所有的状态)存储在外部,从而不需要向父部件发送请求,那么这种方法是可行的。
最大化 Component 接口
Composite 模式的目标是让用户无感知地对等使用 Composite 和 Leaf
- 需求:因此抽象父类 Component 应当提供尽可能多的公共操作并提供缺省实现
- 矛盾:类层次结构的设计原则要求:父类操作集⊆子类操作集,而 Composite 的子组件管理对于 Leaf 没有意义,不应当在 Component 中提供缺省实现
- 解决方法:
- 在 Component 中提供访问子组件的缺省操作,这个缺省的操作不返回任何一个子结点。
- 将 Leaf 视为包含 0 个子组件的 Composite,使用缺省操作
- Composite 重载访问子组件的操作
子组件管理
问题:子组件管理的 Add 和 Remove 操作需要在 Component 还是 Composite 中声明?
- 在 Component 中声明:
- 优点:透明性好,可以一致地使用所有的组件
- 缺点:安全性差,这些操作对于 Leaf 同样可用,用户可能在 Leaf 上调用这些操作
- 如果该组件不允许有子部件,或者 Remove 的参数不是该组件的子结点,通常最好使用缺省方式(可能是产生一个异常)处理 Add 和 Remove 的失败。
- 在 Composite 中声明:
- 优点:安全性好,静态语言的编译器可以确保不会在 Leaf 中 Add 或 Remove 子组件
- 缺点:透明性差,需要区别对待 Composite 和 Leaf。
- 可能会丢失类型信息,不得不执行不安全的强制类型转换(Component→Composite)
- 避免强制类型转换:在 Component 类中声明一个操作
Composite* GetComposite(),缺省返回空指针。然后 Composite 重载并返回 this 指针
删除 Composite 时,注意同时删除其子结点
改善子组件遍历和查找性能
Composite 类可以缓冲存储对它的子结点进行遍历或查找的相关信息。
例子中 Picture 类能高速缓冲存储其子部件的边界框,在绘图或选择期间,当子部件在当前窗口中不可见时,这个边界框使得 Picture 不需要再进行绘图或选择。
一个组件发生变化时,它的父部件原先缓冲存储的信息也变得无效(因此需要通知父组件刷新缓存)
相关模式
- 部件–父部件连接用于 Responsibility of Chain(5.1)模式。
- Decorator(4.4)模式经常与 Composite 模式一起使用。当装饰和组合一起使用时,它们通常有一个公共的父类。因此装饰必须支持具有 Add、Remove 和 GetChild 操作的Component 接口。
- Flyweight(4.6)让你共享组件,但不再能引用其父部件。 Itertor(5.4)可用来遍历 Composite。
- Visitor(5.11)将本来应该分布在 Composite 和 Leaf 类中的操作和行为局部化。
