初探组合模式

代码链接

koonchen/design-patterns/composite

要点

将对象组合成树形结构以表示 部分—整体 的层次结构。 Composite 使得用户对单个对象和组合对象的使用具有一致性。

我们假设存在一个图形编辑器,用户可以组合多个简单组件以形成一个较大的组件,这些组件又可以组合成更大的组件。一个简单的想法是使用 TextLine 这样的图元来定义一些类,与此同时,定义另一些类作为图元的容器类。

然而,这样做的后果是我们将区别对待图元对象和容器对象,而实际上大多数情况下用户认为他们是一样的。区别对待将使得程序更加复杂。 Composite 模式描述了如何使用递归组合,使得用户不必对这些类进行区别对待,如下图:

cover

Composite 模式的关键是一个抽象类,它既可以代表图元也可以代表图元的容器。在图形系统中这个类就是 Graphic ,它声明了一些与特定图形对象相关的操作。在其子类如 LineRectangleText 中定义图元,并实现一些图元操作。而子类如 Picture 定义了图元容器,实现了图元容器的方法。而因为图元和容器使用了相同的父类 ( 接口 ) ,因此容器对象可以递归组合其他 Graphic 子类。其结构如下图:

cover

什么情况下适用这种模式:

  • 想表示对象的 部分—整体 层次结构。
  • 希望用户忽略组合对象与单个对象的不同,用户将统一地适用组合结构中的所有对象。

他的参与者如下:

  • Component ( Graphic ) — 为组合中的对象声明的接口。
  • Leaf ( Rectangle 等 ) — 定义图元对象。
  • Composite ( Picture 等 ) — 定义容器 ( 部件 ) 对象。
  • Client — 通过 Composite 接口操纵组合容器的对象。

现在 composite 结构就很明显了:

cover

composite 模式具有以下特点:

  • 定义了包含基本对象和组合对象的类层次结构:基本对象可以被组合成更复杂的组合对象,而这个组合对象可以被二次组合,这样不断递归下去。
  • 简化客户代码:客户可以一致地使用组合结构和单个对象。客户也无需知道使用的子节点还是组合组件。
  • 使得更容易增加新类型的组件:新定义的 CompositeLeaf 自动地与已有的结构和客户代码一起工作,无需对原有组件进行更改。
  • 使得设计变得更加一般化:容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。比如你希望只是用某些特定的组件来组合一个 Composite ,但是不能通过类型系统来进行约束,而是在运行时进行检查。

实现

在实现 composite 模式时,有以下几点需要注意:

  • 显式的父部件引用:保持从子部件到父部件的引用能简化组合结构的遍历和管理。
  • 共享组件:减少对存储的需求。
  • 最大化 Component 接口: composite 模式的目的之一是使用户不知道他们使用的是 Leaf 还是 Composite 。为了达到这个目的,公共接口应该尽量多定义操作,通常实现缺省。
  • 声明管理子部件的操作:虽然 Composite 类实现了部分操作,但现在我们要作抉择:应该在 Component 中声明这些操作,并使这些操作对 Leaf 有意义呢?还是只在 Composite 和它的子类中声明并定义这些操作?

这需要在安全性和透明性之间做出抉择:

  • 在类层次结构的根部定义子节点管理结构的方法,具有良好的透明性,因为你一致地使用所有的组件,但是这一方法是以安全性为代价的,因为客户有可能会做一些无意义的事情,例如在 Leaf 中增加和删除对象等。
  • Composite 类中定义管理子部件的方法具有良好的安全性,因为在 C++ 这样的静态语言中,在编译时任何任何从 Leaf 中增加或删除对象的尝试都将被发现。但是这损失了透明性,因为现在 LeafComposite 相当于使用了不同的接口。

推荐的是选择透明性,即选择第一种方式。因为第二种方式可能会丢失类型信息,以至于我们不得不将一个组件转换成一个组件,这样的类型转换必定不是类型安全的。

java 实现

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
import java.util.ArrayList;
import java.util.List;

class Component {
void operation() {
System.out.println("Component instance has been created");
}
void add(Component component) {}
void remove(Component component) {}
Component getChild(int index) {
return null;
}
}

class Leaf extends Component {

@Override
void operation() {
System.out.println("Leaf instance has been created");
}
}

class Composite extends Component {
List list = new ArrayList<Component>();
@Override
void add(Component component) {
list.add(component);
}

@Override
void remove(Component component) {
list.remove(component);
}

@Override
Component getChild(int index) {
return (Component) list.get(index);
}
}

public class CompositeClient {
public static void main(String[] args) {
Leaf leaf = new Leaf();
Composite composite = new Composite();
composite.add(leaf);
composite.getChild(0).operation();
}
}

java 的实现比较统一,实现如上。

python 实现

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
class Component(object):
def operation(self): pass
def add(self, obj): pass
def remove(self, obj): pass
def get_child(self, index): pass

class Leaf(Component):
def __repr__(self):
return 'a leaf'
def operation(self):
print('Leaf instance has been created')

class Composite(Component):
def __init__(self):
self._array = list()
def add(self, obj):
self._array.append(obj)
def remove(self, obj):
self._array.remove(obj)
def get_child(self, index):
return self._array[index]

if __name__ == '__main__':
composite = Composite()
leaf = Leaf()
composite.add(leaf)
composite.get_child(0).operation()

其他实现也大同小异。

从其他模式出发:

  • 部件—父部件 的连接可用于 responsibility of chain 模式。
  • decorator 模式经常和 composite 模式一起使用。
  • flyweight 模式可以共享组件,但不再能引用他们的父组件。
  • iterator 可用于遍历 composite 。
  • visitor 将本来应该分布在 CompositeLeaf 类中的操作和行为局部化。