设计模式(21)——组合模式

本文介绍组合模式的概念和应用。

基本思想和原则

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

动机

当需要表示“部分-整体”这种层次结构时,组合模式是比较好的选择。

实现

UI组件树

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
public abstract class UIComponent {
private String name;

public UIComponent(String name) {
this.name = name;
}

public String getName() {
return this.name;
}

public abstract void addChild(UIComponent component);
public abstract void removeChild(UIComponent component);
public abstract ArrayList<UIComponent> getChildren();
}

public class Container extends UIComponent {
private ArrayList<UIComponent> children = new ArrayList<UIComponent>();

public Container(String name) {
super(name);
}

@Override
public void addChild(UIComponent component) {
this.children.add(component);
}

@Override
public void removeChild(UIComponent component) {
this.children.remove(component);
}

@Override
public ArrayList<UIComponent> getChildren() {
return this.children;
}
}

public class Component extends UIComponent {
public Component(String name) {
super(name);
}

@Deprecated
public void addChild(UIComponent component) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}

@Deprecated
public void removeChild(UIComponent component) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}

@Deprecated
public ArrayList<UIComponent> getChildren() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}

public class Test {
public static void main(String[] args) {
UIComponent window = compositeUITree();
String info = displayTree(window);
System.out.println(info);
}

public static UIComponent compositeUITree() {
UIComponent window = new Container("window");
UIComponent panel1 = new Container("panel1");
UIComponent listView = new Container("listView");

window.addChild(panel1);
window.addChild(listView);

UIComponent imageView1 = new Component("imageView1");
UIComponent textView1 = new Component("textView1");

panel1.addChild(imageView1);
panel1.addChild(textView1);

UIComponent panel2 = new Container("panel2");
UIComponent imageView2 = new Component("imageView2");
UIComponent textView2 = new Component("textView2");
panel2.addChild(imageView2);
panel2.addChild(textView2);

UIComponent panel3 = new Container("panel3");
UIComponent imageView3 = new Component("imageView3");
UIComponent textView3 = new Component("textView3");
panel3.addChild(imageView3);
panel3.addChild(textView3);

listView.addChild(panel2);
listView.addChild(panel3);

return window;
}

public static String displayTree(UIComponent root) {
String info = "";
for (UIComponent c:root.getChildren()) {
if (c instanceof Container) {
info += c.getName() + "\n" + displayTree(c);
} else {
info += c.getName() + "\n";
}
}
return info;
}
}

输出如下:

1
2
3
4
5
6
7
8
9
10
panel1
imageView1
textView1
listView
panel2
imageView2
textView2
panel3
imageView3
textView3

上面的代码模拟了一个UI组件树,使用组合模式可以非常容易地模拟这种结构。首先需要组装这棵树,当需要遍历整个组件树时,可以使用递归的方式遍历,就如displayTree中那样。实际项目中很多情境下会用到组合模式,比如公司人员层级结构、控件树等,当然对象之间的关系一般会保存在数据库中,需要时取出后进行组装,这里就不展开说明了。

这个例子是从上至下遍历,如果需要从下往上遍历,可以给每个节点加入一个parent属性。

优点

组合模式对高层模块调用比较友好,高层不需要对具体对象是独立个体还是组合个体加以区分,直接使用即可。另外还可以在树形结构上自由增删节点。