一、组合模式的定义
组合模式又称合成模式,是用来描述部分与整体关系的一种设计模式。它的定义是:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式中的几个角色:
(1)Component抽象构件
定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。
(2)Leaf叶子构件
叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。
(3)Composite树枝构件
树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。
二、组合模式的通用代码
抽象构件

public abstract class Component {
//个体和整体都具有的共享
public void doSomething() {
//编写业务逻辑
}

}
树枝构件

public class Composite extends Component {
//构件容器
private ArrayList<Component> componentArrayList = new ArrayList<Component>();
//增加一个叶子构件或树枝构件
public void add(Component component) {
this.componentArrayList.add(component);
}
//删除一个叶子构件或树枝构件
public void remove(Component component) {
this.componentArrayList.remove(component);
}
//获得分支下的所有叶子构件和树枝构件
public ArrayList<Component> getChildren() {
return this.componentArrayList;
}

}
叶子构件

public class Leaf extends Component {
/*
* 可以重写父类方法
* public void doSomething() {
*
* }
*/

}
场景类

public class Client {
public static void main(String[] args) {
//创建一个根节点
Composite root = new Composite();
root.doSomething();
//创建一个树枝构件
Composite branch = new Composite();
//创建一个叶子节点
Leaf leaf = new Leaf();
//建立整体
root.add(branch);
branch.add(leaf);
}
//通过递归遍历树
public static void display(Composite root) {
for(Component c:root.getChildren()){
if(c instanceof Leaf) { //叶子节点
c.doSomething();
} else { //树枝节点
display((Composite)c);
}
}
}

}
三、组合模式的应用
组合模式的优点:
- 高层模块调用简单
一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别。也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
- 节点自由增加
如果想增加一个树枝节点、树叶节点是不是都很容易,只要找到它的父节点就行了。非常容易扩展,符合开闭原则,对以后的维护非常有利。
组合模式的缺点:
直接使用了实现类,与依赖倒转原则冲突。
组合模式的使用场景:
- 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
- 从一个整体中能够独立出部分模块或功能的场景。
组合模式的注意事项:
只要是树形结构,就要考虑使用组合模式。
四、组合模式的扩展
1. 组合模式的不同实现
组合模式有两种不同的实现:透明模式和安全模式。
- 安全模式
它是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法。这种方法比较安全,我们通常使用的就是这种模式。
- 透明模式
它是把组合使用的方法放在抽象类中,不管叶子对象还是树枝对象都有相同的结构。通过判断是getChildren的返回值确定是叶子节点还是树枝节点。
这种模式如果处理不当,会在运行期出现问题。因此不建议使用这种方式。
抽象构件

public abstract class Component {
//个体和整体都具有的共享
public void doSomething(){
//编写业务逻辑
}
//增加一个叶子构件或树枝构件
public abstract void add(Component component);
//删除一个叶子构件或树枝构件
public abstract void remove(Component component);
//获得分支下的所有叶子构件和树枝构件
public abstract ArrayList<Component> getChildren();

}
叶子构件
public class Leaf extends Component {
@Deprecated
public void add(Component component) throws UnsupportedOperationException {
//空实现,直接抛弃一个"不支持请求"异常
throw new UnsupportedOperationException();
}
@Deprecated
public void remove(Component component)throws UnsupportedOperationException {
//空实现
throw new UnsupportedOperationException();
}
@Deprecated
public ArrayList<Component> getChildren()throws UnsupportedOperationException {
//空实现
throw new UnsupportedOperationException();
}

}
加Deprecated注解的作用:在编译器期告诉调用者,该方法已经失效,可能会出现错误,在运行期也会抛出UnsupportedOperationException的异常。
在透明模式下,遍历树形结构比较容易,不用进行强制类型转换。

public class Client {
//通过递归遍历树
public static void display(Component root) {
for(Component c:root.getChildren()) {
if(c instanceof Leaf) { //叶子节点
c.doSomething();
} else { //树枝节点
display(c);
}
}
}

}
当然,透明模式也具有一定的好处,它遵循了依赖倒转原则,方便系统进行扩展。
2. 组合模式的遍历
可以增加一个父结点,从而可实现树结构的前序遍历、中序遍历和后序遍历。