目的
Composite是结构设计模式的一种,允许你像树一样组合对象,并且允许客户端像单个对象一样和这些结构协作。
问题
Composite模式只有在你的业务模型可以被表示为一个树结构时才有意义。
比如,你有两个对象:Product和Box。这个Box可以包含几个Product和一些更小的Box。这些更小的Box也可以包含一些Product或者更小的Box等等。
现在,想象你的Product和Box是订单的一部分。计算订单的总价会非常困难。你拿到了一个大的Box,打开后看到它里面还有:ProductA,ProductB或者其他的Box,让我们再看看它里面有什么…不久,你将会停在一堆胶带和纸盒上,但仍在尝试计算总价。
那么,有更好的方法吗?
解决
Composite模式建议通过让Product和Box都遵循一个拥有getPrice()方法的通用接口来解决这个问题。
对Product来讲,它仅需要返货这个产品的价格。但是Box需要有更多的有趣行为。一个Box需要遍历它的内容并且寻味每一个商品(item)的价格。所以,如果一个商品是产品,它会立即返回价格。如果是一个更小的Box,它也需要遍历它自己商品的价格直到返回一个总价。一旦小计计算出来,Box甚至可以对总价做一些额外操作,像包装价之类。
现在,通过这个途径我们不必太过关心树的具体对象。不管是单个Product还是复杂的Box,你通过一个通用接口来操作它。并且,这个结构可以自己传递请求。
现实世界的类比
军事结构
大多数国家的军队看起来就像组合树。最底层,是一些士兵。它们组合成班。班组合成排。排组合成师。最后,几个师组成军队。
命令下达给层级的最高层,然后层层向下传递直到每个士兵都知道他们要做什么。
结构
Component为这个树结构的所有简单的和复杂的元素声明一个通用的接口。
Leaf是一个树的一个基本元素,它没有子节点。
因此它们没有任何委托对象,叶子节点通畅要做大多真正的工作。Container(也叫做Composite)是一个有子节点的元素:叶子(Leaves)或其他容器(Container 或 Composite组合)。容器不需要知道子节点的类型,因为它们都实现了Component接口。
收到请求后,容器把它们委托给子节点,然后在返回给客户端之前运行并合计结果。
Client通过Component接口来使用树上的所有元素。
因此,客户端不需要关心它到底是在用简单的叶子还时复杂的容器。
伪代码
在这个例子中,Composite模式帮助我们实现堆叠几何形状。
CompoundGraphic是一个容器,由任意数量的子形状,包括其他组合形状。组合形状和简单形状拥有相同的方法。但是,组合形状自己并不做实际的工作,它把请求传递给子形状,递归遍历自己的叶子节点和组合形状下的子节点。最后容器合并结果返回给客户端。
客户端通过接口和所有形状协作,这些形状都遵循这个接口。这样,客户端代码可以与非常复杂的结构一起工作,而不需要耦合具体的树形元素类。
1 | // Common interface for all components. |
适用性
当你需要实现一个像树一样有着简单元素和容器的结构
Composite模式提供两种基本元素:简单叶子和可以存储其他叶子或者其他容器等的复杂容器。模式强制容器和它的子元素遵循通用的接口,这样就允许递归整个树结构操作。当客户端应该统一处理简单和复杂的元素时。
感谢通用叶子和容器均遵循了通用接口,客户端代码不需要关心协作对象的类型。
如何实现
确保你的业务逻辑可能够被当作树结构。尝试把它们分离成简单元素和容器。切记,容器能够包含基本元素和其他容器。
定义Components(组件)的通用接口。它应该包含对简单和复杂组件来讲都合理的操作。
创建代表基本组件的Leaf(叶子)类。顺便说下,一个程序中可以有多个叶子类。
创建拥有可以存储子组件(数组)的Container类。这个字段可以存储叶子和容器,所以它要被声明为Component类型。
在实现Component接口的方法时,记住,Container应该把它的多数工作委托给子组件。最后,为Container实现add/remove子元素的方法。
记住,这些操作应该被放在Component接口中。它喂饭了接口分离原则,因为这些方法在Leaf类中是空的。但是另一方面,树中所有的组件变得与客户端的立场相当。
优点
简化必须与复杂树结构交互的客户端代码。
添加新组件类型变的简单。
缺点
- 创建了一个太过通用的类设计。
和其他模式的关系
Builder可以用来一步一步的构建一个复杂的Composite。
Chain of Resopnsibility通常和Composite结合使用。在这种情况下,组件的父级可以作为其后继。
Iterator可以用来遍历Composite树。
Visitor用来操作Composite树的实体。
Flyweight场合Composite结合使用来实现叶子结点共享和保存RAM。
Composite和Decorator结构图很相似,因为它们都依赖递归组合来组织开放数量的对象。
Decorator可以看作只有一个组件的弱化Composite。然而,Decorator给对象赋予了额外责任,而Composite仅是对所有拥有相同行为的子元素做了“合并”。但是它们也可以合作:Composite能够用Decorator改变树组件的行为。
使用Composite和Decorator模式使得设计笨重,可以使用Prototype来简化。它允许克隆复杂结构,而不是重新构造它们。
Java中模式的使用
用例:Composite模式在Java代码中很常见。它常用来表示用户接口组件的层级或者和图像一起使用的代码。
这有一些在标准Java苦衷使用组合的例子:
java.awt.Container#add(Component) (几乎遍及Swing组件)
javax.faces.component.UIComponent#getChildren() (几乎遍及JSF UI 组件)
鉴别:组合模式很容易识别,它们的行为方法都有一个在树结构中具有相同抽象/接口类型的实例。