1、意图
将对象组合成树形结构以表示“部分 - 整体”的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性。
2、动机
在图形应用程序中,用户可以使用简单的图元组件组合成较大的组件,这些组件又可以组合成更加复杂的组件。Composite描述了如何使用递归组合,使得用户不必对这些类进行区别。它的关键是一个抽象类,既可以代表简单的图元,又可以代表图元的组合。
3、适用性
-
想表示对象的“部分 - 整体”的层次结构
-
希望用户忽略组合对象和单个对象的不同,用户将统一使用组合结构中的所有对象。
4、结构
5、参与者
-
Component:
- 为组合中的对象声明接口;
- 在适当的情况下,实现所有类公共接口的缺省行为;
- 声明一个接口用于访问和管理Component的子组件;
- (可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。 -
Leaf:
- 在组合中表示叶节点对象,叶节点没有子节点;
- 在组合中定义其行为。 -
Composite:
- 定义组合部件的行为;
- 存储子部件;
- 实现Component接口中与子部件有关的操作。 -
Client:
- 通过Component接口操纵所有部件对象。
6、协作
用户使用Component类接口与组合结构中的对象进行交互。如果接收者是一个Leaf,则直接处理请求。如果接收者是Composite,它通常将请求发送给它的子部件,在转发请求之前或/与之后可能执行一些辅助操作。
7、效果
-
定义了包含基本对象和组合对象的类层次结构。客户端代码中,任何用到基本对象的地方都可以用组合对象。
-
简化客户端代码。客户可以一致的使用组合对象和单个对象。
-
更容易增加新类型的组件。
-
使你的设计变得更加一般化。如果你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。
8、实现
-
显式的父部件引用。保持“从子部件到父部件的引用”能简化组合结构的遍历和管理。父部件引用可以简化结构的上移和组件的删除,同时父部件引用也支持Chain of Responsibility模式。
通常在Component类中定义父部件引用。Leaf和Composite类可以继承这个引用以及管理这个引用的那些操作。
对于父部件的引用,必须维护一个不变式,即一个组合的所有子节点以这个组合为父节点,反之该组合以这些节点为子节点。最容易的办法是,仅当在一个组合中增加或删除一个组件时,才改变这个组件的父部件。
一句话:在Component基类中增加一个父部件指针,当操作Composite中的Add和Remove时,改变该指针。 -
共享组件。共享组件是很有用的,比如可以减少对存储的需求。但是当一个组件只有一个父部件时,很难共享组件。
一个可行的解决办法是为子部件存储多个父部件,但当一个请求在结构中向上传递时,这种方法对导致多义性。Flyweight模式讨论了如何修改设计以避免将父部件存储在一起。另外,如果子部件可以将一些状态(或所有状态)存储在外部,从而不需要向父部件发送请求,那么这种方法是可行的。 -
最大化Component接口。对于仅对Composite类有意义的操作,定义一个某人的缺省实现,Leaf使用缺省实现,Composite重新实现这些操作。
-
声明管理子部件的操作。问题:是在Component中声明Add和Remove操作并使之对Leaf类有意义,还是只在Composite中声明并定义这些操作呢?前者具有很好的透明性,可以一致的使用所有的组件,但是客户可能会对Leaf中执行Add和Remove操作;后者具有良好的安全性,但是使Leaf和Composite具有不同的接口,丧失了透明性。
一种办法是在Component中提供一个Composite* GetComposite()操作,来查询当前组件是不是一个组合,根据返回值安全的执行Add和Remove操作。
提供透明性的唯一方法是在Component定义缺省的Add和Remove操作。
提供透明性的唯一方法是在Component中定义缺省的Add和Remove操作,使用缺省方式处理Add和Remove失败(可能是产生一个异常)。Add失败时,可能会造成内存泄露。 -
Component是否应该实现一个Component列表。在基类中存放子类指针,对于叶节点来说会造成空间浪费,只有当结构中子类数目相对较少时,才值得使用这种方法。
- 子部件排序。如果需要考虑子节点的顺序时,必须仔细地设计子节点的访问和管理接口,以便管理子节点序列。参考Iterator模式。
- 使用高速缓冲存储改善性能。如果需要对组合进行频繁的遍历和查找,Composite可以缓冲存储对它的子节点进行遍历或查找的相关信息。因此,此时需要定义一个接口来通知父部件它所缓冲存储的信息无效或需要立即更新。
- 应该由谁删除Component。在没有垃圾回收机制的语言中,当一个Component被销毁时,通常最好由Composite负责删除其子节点。但有一种情况除外,即Leaf对象不会改变,因此可以被共享。
- 存储组件最好使用那一种数据结构。
9、代码示例
10、相关模式
-
通常部件 - 父部件连接用于Chain of Responsibility模式
-
Decorator模式通常与Composite模式一起使用,它们通常有一个公共的父类。因此Decorator必须支持具有Add、Remove、GetChild等Component接口
-
Flyweight模式让你共享组件,但不再能引用他们的父部件
-
Iterator模式用于遍历Composite
-
Visitor模式将本来应该分布在Composite和Leaf中的操作和行为局部化。