今天我们来学习结构型设计模式中的组合模式,组合模式的应用场景较为特殊,要求数据必须能表示成树形结构,这就导致了组合模式在实际的项目开发中并不那么常用。
概述
组合模式:(Composite Design Pattern)将一组对象组织(Compose)成树形结构,以表示一种部分-整体
的层次结构。组合让客户端可以统一单个对象和组合对象的处理逻辑。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下。
其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。
何时使用:
- 当你的程序结构有类似树一样的层级关系时,例如文件系统,视图树,公司组织架构等等
- 当你要以统一的方式操作单个对象和由这些对象组成的组合对象的时候。
UML 类图:
角色组成:
抽象根节点
(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。树枝节点
(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。叶子节点
(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
通用代码
抽象根节点 Component.java
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void otherMethod();
public abstract void addChild(Component component);
public abstract void removeChild(Component component);
public abstract Component getChild(int index);
}
树枝节点 Composite.java
public class Composite extends Component {
private List<Component> mLists = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void otherMethod() {
System.out.println("name: " + name);
if (mLists != null && mLists.size() > 0) {
for (Component component : mLists) {
component.otherMethod();
}
}
}
@Override
public void addChild(Component component) {
mLists.add(component);
}
@Override
public void removeChild(Component component) {
mLists.remove(component);
}
@Override
public Component getChild(int index) {
return mLists.get(index);
}
}
叶子节点 Leaf.java
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void otherMethod() {
System.out.println("name: " + name);
}
@Override
public void addChild(Component component) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
@Override
public void removeChild(Component component) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
@Override
public Component getChild(int index) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
}
客户端 Client.java
public class Client {
private static Component constructTree() {
//构造一个根节点
Component root = new Composite("根节点 Root");
//构造树枝节点
Component branchA = new Composite("树枝节点 branchA");
Component branchB = new Composite("树枝节点 branchB");
//构造叶子节点
Component leafA = new Leaf("叶子节点 leafA");
Component leafB = new Leaf("叶子节点 LeafB");
//将叶子节点添加至树枝节点中
branchA.addChild(leafA);
branchB.addChild(leafB);
//将树枝节点添加进根节点
root.addChild(branchA);
root.addChild(branchB);
return root;
}
public static void main(String[] args) {
Component component = constructTree();
component.otherMethod();
}
}
结果:
name: 根节点 Root
name: 树枝节点 branchA
name: 叶子节点 leafA
name: 树枝节点 branchB
name: 叶子节点 LeafB
组合模式分类
上面通用代码实际上是组合模式的其中一种形式:透明组合模式
,除此之外,组合模式还有另一种形式:安全组合模式
。
透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 Component 声明了 addChild()、removeChild() 、getChild() 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 addChild()、removeChild() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错。
安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Composite 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
总结
组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。
组合模式的缺点:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;