意图
以树形结构表示“整体-部分”的层次结构。对单个对象和组合对象拥有一致性的操作体验。
动机
在一些场景下,用户可以使用简单的组件组合复杂的组件,这些复杂的组件又可以组合成更复杂的组件。最简单的实现方法,是对于简单组件分别使用一些类实现,然后再定义一些类,作为这些简单类的容器。
这存在一个明显的问题:使用这些类的代码要区别简单组件和容器,而实际上大多数使用情况下,用户认为他们应用有一致的行为。加入这些区别,使得操作复杂化。
组合(Composite)模式,描述了如何使用递归组合,使用户不需要区分这些类。
组合模式的关键在于一个抽象的类,即可以代表简单组件,又可以代表容器。所有的组件和容器都继承自此抽象类,同时容器类又是基类对象的聚合。
适用性
- 表示对象的部分-整体层次结构
- 希望用户忽略组合对象与简单组件的区别
结构
类图
结构图
参与者
- Component
- 为组合中的对象声明接口
- 实现所有类的默认行为
- 声明一组接口用于访问和管理 Component的子组件
- (可选)在递归结构中定义一个访问父组件的接口,并实现
- Leaf
- 表示叶子节点对象,没有子节点
- 定义叶子节点的行为
- Composite
- 定义有子组件的组件的行为
- 存储子组件
- 实现 Component 中有关于子组件的接口
- Client
- 通过 Component 的接口操作组合组件的对象
协作
Client 使用 Component 的接口与组合结构中的对象交互,叶子节点直接处理请求。Composite 节点通常会转发请求给子节点,转发前后可能会有一些辅助操作
效果
- 定义了包含基本组件和组合组件的类层次结构
- 简化了客户端代码
- 更容易的添加新类型组件
- 设计变得更一变化
实现折衷
- 显式的父组件引用(parent reference)
- 优势
管理组合结构简单化,方便于结构上移和删除操作。支持 Chain of Responsibility 模式 - 是否是现在 Component 中
如果能够保证父引用的不变式,则可以。这表明,任何一个组件,他的父组件引用所指向的组件,必须将其视为子组件。
最简单的实现方法是,只有在一个组件从一个Composite中添加或移除时,才改变其父引用
- 优势
- 共享组件
- 优势
节省存储空间 - 难点
组件有多个父组件时,问题变得棘手。解决方法是 Flyweight 模式
- 优势
- 最大化组件接口
- 优势
使得客户端能够忽略不同组件的区别 - 劣势
违反了基类设计原则,有很多操作Leaf用不到
- 优势
- 何处声明子组件管理操作
- Component
带来透明性(transparency), 丧失安全性(safety)。因为client可能会访问叶子节点的这些操作 - Composite
带来安全性,丧失透明性。任何对叶子节点的操作,导致便以失败。但是,客户端使用时要区分是Leaf还是Composite。 - 总结
组合模式更青睐于透明性。对于安全性的保证可以引入函数Composite* GetComposite()
,当时一个Leaf时,返回空指针。
真正的便捷,可以通过定义通用的Add()
和Remove()
接口来解决。默认行为直接返回失败信息,而Composite通过重写这些函数达到应有的效果。
- Component
- 在Component中放入Component列表
- 优势
便于管理子组件 - 劣势
浪费空间 - 折衷
只有在Leaf少的时候,这么做才合适
- 优势
- 子组件有序
需要有序时,使用Iterator模式 - 缓存
缓存能够提升遍历时的性能。但要注意,在节点改变时,通知父节点缓存已经失效。 - 删除组件责任
父组件在自身被删除时,应负责删除自己的子组件。除非子组件是不可变的或者共享的 - 存储组件的最佳数据结构
依据不同场景灵活变化。有时甚至需要自己实现一些管理接口和结构。可以参见Interpreter模式