Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly.

(将一组对象组织(Compose)成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端(在很多设计模式书籍中,“客户端”代指代码的使用者。)可以统一单个对象和组合对象的处理逻辑。)

组合模式的数据必须能表示成树形结构,这也导致了这种模式在实际的项目开发中并不那么常用。但是,一旦数据满足树形结构,应用这种模式就能发挥很大的作用,能让代码变得非常简洁。

1 组合模式 UML

图片来源于《Android 源码设计模式解析与实战》

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
/**
* 抽象根节点,为组合中的对象声明接口。
* 在适当的情况下,实现所有类共有接口的缺省行为。
* 声明一个接口用于访问和管理 Component 的子节点。可在递归结构中定义一个接口,用于访问一个父节点,并在合适的情况下实现它。
*/
public abstract class Component {
protected String name;

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

public abstract void doSomething();

public abstract void addChild(Component child);

public abstract void removeChild(Component child);

public abstract Component getChildren(int index);
}

/**
* 定义有子节点的那些枝干节点的行为,存储子节点,在 Component 接口中实现与子节点有关的操作。
*/
public class Composite extends Component {
private List<Component> components = new ArrayList<>();

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

@Override
public void doSomething() {
System.out.println(name);
if (null != components) {
for (Component c : components) {
c.doSomething();
}
}
}

@Override
public void addChild(Component child) {
components.add(child);
}
@Override
public void removeChild(Component child) {
components.remove(child);
}
@Override
public Component getChildren(int index) {
return components.get(index);
}
}

/**
* 在组合中表示叶子节点对象,叶子节点没有子节点,在组合中定义节点对象的行为。
*/
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}

@Override
public void doSomething() {
System.out.println(name);
}

@Override
public void addChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}

@Override
public void removeChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}

@Override
public Component getChildren(int index) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
}

//构造一个根节点
Component root = new Composite("Root");
//构造连个枝干节点
Component branch1 = new Composite("Branch1");
Component branch2 = new Composite("Branch2");
//构造两个叶子节点
Component leaf1 = new Leaf("Leaf1");
Component leaf2 = new Leaf("Leaf2");

//将叶子节点添加至枝干节点
branch1.addChild(leaf1);
branch2.addChild(leaf2);

//将枝干节点添加至根节点中
root.addChild(branch1);
root.addChild(branch2);

root.doSomething();

像这样将组合所使用的方法定义在抽象类的方式称为透明的组合模式,而还有一种组合模式则称为安全的组合模式。

透明组合模式中不管是叶子节点还是枝干节点都有着相同的结构,那意味着我们无法通过 getChildren 方法得到子节点的类型,而必须在方法实现的内部进行判断。

安全的组合模式在使用时都必须直接使用具体的实现类。UML 如下,下面的 文件目录 例子是一个安全的组合模式。

图片来源于《Android 源码设计模式解析与实战》

2 组合模式的简单实现

设计一个类来表示文件系统中的目录,能方便地实现下面这些功能:

  • 动态地添加、删除某个目录下的子目录或文件;
  • 统计指定目录下的文件个数;
  • 统计指定目录下的文件总大小。
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
112
113
public abstract class FileSystemNode {
protected String path;

public FileSystemNode(String path) {
this.path = path;
}

//文件数
public abstract int countNumOfFiles();

public abstract long countSizeOfFiles();

public String getPath() {
return path;
}
}

public class File extends FileSystemNode {
public File(String path) {
super(path);
}

@Override
public int countNumOfFiles() {
return 1;
}

@Override
public long countSizeOfFiles() {
java.io.File file = new java.io.File(path);
if (!file.exists()) return 0;
return file.length();
}
}

public class Directory extends FileSystemNode {
private List<FileSystemNode> subNodes = new ArrayList<>();

public Directory(String path) {
super(path);
}

@Override
public int countNumOfFiles() {
int numOfFiles = 0;
for (FileSystemNode fileOrDir : subNodes) {
numOfFiles += fileOrDir.countNumOfFiles();
}
return numOfFiles;
}

@Override
public long countSizeOfFiles() {
long sizeofFiles = 0;
for (FileSystemNode fileOrDir : subNodes) {
sizeofFiles += fileOrDir.countSizeOfFiles();
}
return sizeofFiles;
}

public void addSubNode(FileSystemNode fileOrDir) {
subNodes.add(fileOrDir);
}

public void removeSubNode(FileSystemNode fileOrDir) {
int size = subNodes.size();
int i = 0;
for (; i < size; ++i) {
if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
break;
}
}
if (i < size) {
subNodes.remove(i);
}
}
}

/**
* /
* /wz/
* /wz/a.txt
* /wz/b.txt
* /wz/movies/
* /wz/movies/c.avi
* /xzg/
* /xzg/docs/
* /xzg/docs/d.txt
*/
Directory fileSystemTree = new Directory("/");
Directory node_wz = new Directory("/wz/");
Directory node_xzg = new Directory("/xzg/");
fileSystemTree.addSubNode(node_wz);
fileSystemTree.addSubNode(node_xzg);

File node_wz_a = new File("/wz/a.txt");
File node_wz_b = new File("/wz/b.txt");
Directory node_wz_movies = new Directory("/wz/movies/");
node_wz.addSubNode(node_wz_a);
node_wz.addSubNode(node_wz_b);
node_wz.addSubNode(node_wz_movies);

File node_wz_movies_c = new File("/wz/movies/c.avi");
node_wz_movies.addSubNode(node_wz_movies_c);

Directory node_xzg_docs = new Directory("/xzg/docs/");
node_xzg.addSubNode(node_xzg_docs);

File node_xzg_docs_d = new File("/xzg/docs/d.txt");
node_xzg_docs.addSubNode(node_xzg_docs_d);

System.out.println("/ files num:" + fileSystemTree.countNumOfFiles());
System.out.println("/wz/ files num:" + node_wz.countNumOfFiles());

组合模式的定义:“将一组对象(文件和目录)组织成树形结构,以表示一种 ‘部分 - 整体’ 的层次结构(目录与子目录的嵌套结构)。组合模式让客户端可以统一单个对象(文件)和组合对象(目录)的处理逻辑(递归遍历)。”

这种组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。

3 Android 源码中的模式实现

在 Android 中,说到树,脱口而出的就是 View 树,是树形结构跑不了了!先看看 UML

图片来源于《Android 源码设计模式解析与实战》

这里省略了 View 和 ViewGroup 类中的一些方法,在 Android 的这个视图层级中,容器一定是 ViewGroup ,而且只有 ViewGroup 才能包含其他的 View ,比如 LinearLayout 能包含 TextView 、Button、CheckBox 等,但是反过来 TextView 是不能包含 LinearLayout 的,因为 TextView 直接继承于View,其并非一个容器。这里 View 的视图层级中使用到的其实是一种安全的设计模式

1
2
3
4
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

从继承的角度来说 ViewGroup 拥有 View 类所有的非私有方法,既然如此,两者的差别就在于 ViewGroup 所实现的 ViewParent 和 ViewManager 接口上,而事实也是如此,ViewManager 接口定义了 addView 、removeView 等对子视图操作的方法。

而 ViewParent 则定义了刷新容器的接口 requestLayout 和其他一些焦点事件的处理的接口。

ViewGroup 除了所实现的这两个接口与 View 不一样外,还有重要的一点就是 ViewGroup 是抽象类,其将 View 中的 onLayout 方法重置为抽象方法,也就是说容器子类必须实现该方法来实现布局定位,我们知道 View 中的该方法是个空实现,因为对于一个普通的 View 来说该方法并没有什么实现价值,但是 ViewGroup 就不一样,要必须实现。除此之外,在 View 中比较重要的两个测绘流程的方法 onMeasure 和 onDraw 在 ViewGroup 中都没有被重写,相对于 onMeasure 方法,在 ViewGroup 中增加了一些计算子 View 的方法,如 measureChildren、measureChildrenWithMargins 等;而对于 onDraw 方法,ViewGroup 定义了一个 dispatchDraw 方法来调用其每一个子 View 的 onDraw 方法,由此可见,ViewGroup 真的就象一个容器一样,其职责只是负责对子元素的操作而非具体的个体行为。

感谢

设计模式之美

《Android 源码设计模式解析与实战》

以及上文中的链接