logo头像

👨‍💻冷锋のIT小屋

Java设计模式(12)-组合模式

tree

1. 整体与部分的关系

很多场景下,需要考虑整体和部分之间的关系,在对其进行管理功能设计时,需要考虑到灵活性和扩展性,而继承关系往往不能很好地解决这些问题。一个最典型的例子就是:组织机构。比如,就公司而言,总公司可能下设有分公司,分公司下又有办事处,而办事处、分公司、总公司可能都会存在一些职能部门。很明显,组织机构是一个整体和部分的关系,并且它是一颗树状结构。

composite pattern org tree
Figure 1. 复杂的组织机构树状结构

设计这样的结构,你是否会使用继承关系?如让分公司继承总公司、办事处继承分公司,这样往往是从机构的大小维度来划分的,子类除了具备父类的功能外,还能够独立扩展功能。但是,在组织机构树中,其实每个节点(机构)所承担只能都是差不多的,换言之,总公司、分公司、办事处三者其实并没有父子关系,在管理职能上他们的功能是相同的,比如都能添加、删除、查询子的机构节点。所以采用继承关系来设计其实是不合适的。

还有很多整体-部分关系的例子,比如学校、学院和系的关系,电脑组装商家可以出售整机也可以出售配件,文字处理软件中单个文字和整段甚至整篇文字的处理方式差不多,又如Java AWT、Swing中的容器组件和简单控件的关系。其实,这一类问题,最终解决的都是整体和部分被一致对待的问题,组合模式很好的解决了这个问题。

2. 组合模式简介

在Design Patterns一书中对组合模式的定义如下:

组合模式(Composite Pattern),将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

组合模式是一种结构型设计模式,其"对象组合成树形结构"定义代表了对整体部分关系的抽象,将树形结构抽象为三个部分:根节点、树枝节点和叶子节点,其中,根节点、树枝节点可以添加子节点,而叶子节点不能添加子节点。因此,组合模式有两种方式:透明方式和安全方式。

在说明这两种方式之前,先看看组合模式的角色:

  • 抽象构件(Component):对象组合的高度抽象构件,声明了对象操作的公共接口。透明方式和安全方式对节点管理方法的声明存在差异:透明方式会声明管理节点的接口,如添加、删除,但是安全方式不会声明管理接口,而是将其移交到树枝节点声明和实现,具体后边再说;

  • 树枝构件(Composite):树形结构的分支节点,实现抽象构件,同时承担管理子节点职能,如实现添加、删除节点的方法;

  • 树叶构件(Leaf):树形结构的叶子几点,没有子节点,所以不承担管理职能,它实现添加、删除等管理职能方法是没有意义的;

透明方式和安全方式主要区别在于:客户端是否需要区分树枝构件(Composite)和树叶构件(Leaf),透明方式则不区分,而安全方式则需要区分。

2.1. 透明方式

透明方式,不区分树叶构件和树枝构件,两者都实现构建构件的api,因此,树形结构管理职能的API(添加、修改、删除等)声明在抽象构件中,客户端使用时不需要区分树枝和树叶,因为他们具有相同的方法。类图如下:

composite pattern tmfs
Figure 2. 透明方式类图

这种方式的好处在于,客户端不需要判断树叶构件和树枝构件,因此对客户端是统一的或者说透明的,方便管理;但是前边我们说过,树叶构件是没有子节点的,它不承担管理职能,实现管理职能的方法没有意思,因此我们只能进行方法空实现或者抛出异常,这一点不太友好。

2.2. 安全方式

相对于透明方式,安全方式显得比较保守。它只在抽象构件中声明公共方法,管理职能的方法放到树枝构件中进行声明和实现,这样树叶构件就不会存在管理职能的方法了。类图如下:

composite pattern aqfs
Figure 3. 安全方式类图

这种方式的优点在于,树叶构件不需要对管理职能方法做无意义的空实现或抛异常,代码更严谨,但是其缺点也很明显:客户端需要明确知道调用的是树叶构件还是树枝构件,相对而言比较麻烦。

3. 应用示例

看一个例子。大学中分为多个学院,每个学院下又存在多个系(专业),我们看看如果运用组合模式来实现。

分析:学校、学院、系属于整体和部分关系,我们将其看做同一个整体并进行抽象,得到一个组织机构接口,让他们分别实现该接口。同时,学校、学院下都包含子节点,因此他们可以看做树枝构件,内部持有一个List来保存子节点数据,而系下不再有子节点,它属于树叶构件。

composite pattern yysl
Figure 4. 学校学院系类图

类图中,Organization为抽象构件,声明了addremove两个管理职能方法,而业务处理方法只有一个print方法,用来打印自身和其下的所有子节点。

从类图可以看出,我们使用的是组合模式的透明方式。

具体代码如下:

1、抽象构件:

1
2
3
4
5
6
7
interface Organization {
void add(Organization org);

void remove(Organization org);

void print();
}

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
// 学校
class University implements Organization {
private final String name;
private final List<Organization> colleges = new ArrayList<>(); (1)

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

@Override
public void add(Organization org) {
this.colleges.add(org);
}

@Override
public void remove(Organization org) {
this.colleges.remove(org);
}

@Override
public void print() { (2)
System.out.println(this.name);
for (Organization college : colleges) {
college.print();
}
}
}
1 用一个List结构存储其子节点信息。
2 先打印自身,然后在调用每一个子节点的print()方法。
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 College implements Organization {
private final String name;
private final List<Organization> depts = new ArrayList<>();

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

@Override
public void add(Organization org) {
this.depts.add(org);
}

@Override
public void remove(Organization org) {
this.depts.remove(org);
}

@Override
public void print() {
System.out.println("--" + this.name);
for (Organization dept : depts) {
dept.print();
}
}
}

3、树叶构件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Department implements Organization {
private final String name;

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

@Override
public void add(Organization org) { (1)
throw new UnsupportedOperationException("系不能进行添加操作")
;
}

@Override
public void remove(Organization org) { (1)
throw new UnsupportedOperationException("系不能进行删除操作")
;
}

@Override
public void print() {
System.out.println("----" + this.name);
}
}
1 对于管理方法,直接抛异常

4. 总结

  1. 组合模式将整体部分一致对待,简化了客户端操作;

  2. 具备良好的扩展性,组合对象的层次结构可以扩展,修改组合对象时只需要调整层次关系,客户端代码不需要改动;

  3. 组合模式适用于整体部分关系可以抽象与树形结构的场景,并且树枝和树叶差异不大的情况适用。

支付宝打赏 微信打赏

赞赏是不耍流氓的鼓励