设计模式 组合模式

本文将介绍设计模式中的组合模式。

一、什么是组合模式?

在软件中,常常可以见到树形结构,对于开发人员而言,我们希望的是:

  • 可以一致地处理非叶节点和叶子节点两种组成元素

    因为对它们的区别处理会使程序复杂度增加

  • 对某个节点调用业务方法时,能够自行进行递归调用

    以免在外部额外书写树的递归访问代码

此时便可以运用组合模式,它使用户可以同一对待单个对象和组合对象。

二、实现

1. 结构

2. 角色

  • Component 抽象构件:
    • 叶子构件和容器构件的接口
    • 定义了 Composite 容器构件访问和管理子构件的行为
    • 定义了 Leaf 叶子构件进行的业务操作
  • Leaf 叶子构件:
    • 表示叶子节点
    • 没有子节点
    • 由于 Component 抽象构件中定义了访问和管理子构件的行为,但这些行为实际上是不存在于 Leaf 叶子构件中的,因此可以通过异常等方式进行处理
    • 实现了业务操作
  • Composite 容器构件:
    • 表示容器节点
    • 包含子节点,其子节点可以是叶子节点,也可以是容器节点
    • 实现了 Component 抽象构件中定义的访问和管理子构件的行为
    • 本身不进行业务操作,会通过遍历调用的方法,调用所有子节点的业务操作

3. 简单示例

Component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Component {
/**
* 增加成员
*/
public abstract void add(Component component);

/**
* 删除成员
*/
public abstract void remove(Component component);

/**
* 获取子构件
*/
public abstract Component getChild(int index);

/**
* 业务方法
*/
public abstract void operation();
}

Leaf:

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
public class Leaf extends Component {
/**
* 增加成员
*/
public void add(Component component) {
// 抛出异常
}

/**
* 删除成员
*/
public void remove(Component component) {
// 抛出异常
}

/**
* 获取子构件
*/
public Component getChild(int index) {
// 抛出异常
return null;
}

/**
* 业务方法
*/
public void operation() {
···
}
}

Composite:

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
public class Composite extends Component {
private List<Component> list = new ArrayList<>();

/**
* 增加成员
*/
public void add(Component component) {
list.add(component);
}

/**
* 删除成员
*/
public void remove(Component component) {
list.remove(component);
}

/**
* 获取子构件
*/
public Component getChild(int index) {
return list.get(index);
}

/**
* 业务方法
*/
public void operation() {
for (Component component : list) {
list.operation();
}
}
}

三、安全组合模式

在安全组合模式中,

  • Component 抽象构件声明业务方法,
    • Leaf 叶子构件实现业务方法
    • Composite 容器构件在业务方法中遍历调用子元素的业务方法
  • Component 抽象构件不会声明访问和管理子构件的方法,
    • Composite 容器构件自行书写访问和管理子构件的方法

在这种做法下,Leaf 不需要额外书写无用的方法,外界也不必对异常调用做处理,是更安全的方式。

但是这个方法存在的问题是:客户端需要区别对待两种元素,其代码复杂性将增加。

四、优缺点

1. 优点

  • 客户端无需区别对待叶子节点和非叶节点两种元素,降低了代码复杂性
  • 客户端无需书写树形结构的访问代码

参考

  • 《Java 设计模式》