0%

设计模式-结构模式之Composite

目的

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,你通过一个通用接口来操作它。并且,这个结构可以自己传递请求。

现实世界的类比

军事结构

大多数国家的军队看起来就像组合树。最底层,是一些士兵。它们组合成班。班组合成排。排组合成师。最后,几个师组成军队。

命令下达给层级的最高层,然后层层向下传递直到每个士兵都知道他们要做什么。

结构

structure

  1. Component为这个树结构的所有简单的和复杂的元素声明一个通用的接口。

  2. Leaf是一个树的一个基本元素,它没有子节点。
    因此它们没有任何委托对象,叶子节点通畅要做大多真正的工作。

  3. Container(也叫做Composite)是一个有子节点的元素:叶子(Leaves)或其他容器(Container 或 Composite组合)。容器不需要知道子节点的类型,因为它们都实现了Component接口。

    收到请求后,容器把它们委托给子节点,然后在返回给客户端之前运行并合计结果。

  4. Client通过Component接口来使用树上的所有元素。
    因此,客户端不需要关心它到底是在用简单的叶子还时复杂的容器。

伪代码

在这个例子中,Composite模式帮助我们实现堆叠几何形状。

CompoundGraphic是一个容器,由任意数量的子形状,包括其他组合形状。组合形状和简单形状拥有相同的方法。但是,组合形状自己并不做实际的工作,它把请求传递给子形状,递归遍历自己的叶子节点和组合形状下的子节点。最后容器合并结果返回给客户端。

客户端通过接口和所有形状协作,这些形状都遵循这个接口。这样,客户端代码可以与非常复杂的结构一起工作,而不需要耦合具体的树形元素类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Common interface for all components.
interface Graphic is
method move(x, y)
method draw()

// Simple component.
class Dot implements Graphic is
field x, y

constructor Circle(x, y) { ... }

method move(x, y) is
this.x += x, this.y += y

method draw() is
Draw a dot at X and Y.

// Components could extend other components.
class Circle extends Dot is
field radius

constructor Circle(x, y, radius) { ... }

method move(x, y) is
this.x = x, this.y = y

method draw() is
Draw a circle at X and Y and radius R.

// The composite component includes methods to add/remove child components. It
// tries to delegates to its children all operations defined in the
// component interface.
class CompoundGraphic implements Graphic is
field children: array of Graphic

method add(child: Graphic) is
Add child to children array.

method remove(child: Graphic) is
Remove child to children array.

method move(x, y) is
For each child: child.move(x, y)

method draw() is
Go over all children and calculate bounding rectangle.
Draw a dotted box using calculated values.
Draw each child.


// Application can operate with specific components or whole groups.
class ImageEditor is
method load() is
all = new CompoundGraphic()
all.add(new Dot(1, 2))
all.add(new Circle(5, 3, 10))
// ...

method groupSelected(components: array of Graphic) is
group = new CompoundGraphic()
group.add(components)
all.remove(components)
all.add(group)
// All components will be drawn.
all.draw()

适用性

  • 当你需要实现一个像树一样有着简单元素和容器的结构
    Composite模式提供两种基本元素:简单叶子和可以存储其他叶子或者其他容器等的复杂容器。模式强制容器和它的子元素遵循通用的接口,这样就允许递归整个树结构操作。

  • 当客户端应该统一处理简单和复杂的元素时。
    感谢通用叶子和容器均遵循了通用接口,客户端代码不需要关心协作对象的类型。

如何实现

  1. 确保你的业务逻辑可能够被当作树结构。尝试把它们分离成简单元素和容器。切记,容器能够包含基本元素和其他容器。

  2. 定义Components(组件)的通用接口。它应该包含对简单和复杂组件来讲都合理的操作。

  3. 创建代表基本组件的Leaf(叶子)类。顺便说下,一个程序中可以有多个叶子类。

  4. 创建拥有可以存储子组件(数组)的Container类。这个字段可以存储叶子和容器,所以它要被声明为Component类型。
    在实现Component接口的方法时,记住,Container应该把它的多数工作委托给子组件。

  5. 最后,为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 组件)

鉴别:组合模式很容易识别,它们的行为方法都有一个在树结构中具有相同抽象/接口类型的实例。

参考

翻译整理自:https://refactoring.guru/design-patterns/composite