组合模式


定义

这节我们将介绍一种全新的设计模式——组合模式。想起“组合”二字,自然联想到了很多,比如:文件和文件夹、容器和组件、火车和车厢、大树的枝干和叶子等等,大自然中组合的例子数不胜数。

什么是组合关系?组合关系:部分与整体的关系,有了整体才有部分,部分不能脱离整体而存在,比如脱离了火车,车厢也无法运转。与组合关系经常做类比的是聚合关系:整体和部分的关系,部分可以单独存在,比如班级和学生,学生脱离班级后还可以加入其他团体而存在。

这种表示“整体与部分”的关系的数据结构,类似于我们经常接触到的树形结构,想象一下:容器里可以包含组件,比如 Panel 面板中可以包含 Button 组件和子面板 Panel,子 Panel 中同样又可以包含 Panel 和 Button,简单的图例关系如下:

这是一个理想情况下可无限循环的结构体,组合模式的出现就是为了让我们以一致的方式来处理个别对象亦或者组合对象,而不必区分对象类型。

也就是说,我们要对树上的节点和叶子进行操作时,它能够提供一种一致的方式,不必纠结它是叶子类型还是节点类型。这里的一致怎么理解呢?其实有一个很重要的知识点或者共识要说明下,那就是不管是 Panel 还是 Button,我们都可以把它归为“页面元素”,也就是说我们不考虑对象类型的情况下,实际上我们都把它当作“页面元素”来处理;另外,我们的Button虽然和 Panel 是同级,但是 Button 可以作为 Panel 的子“页面元素”来存在,这也是很重要的共识。所以这里的一致,就指的是容器和内容的一致。

基于以上几点共识,我们便能使用组合模式来解决类似问题,也就说,当我们的案例牵扯到树形结构或者类似的“部分-整体”关系的时候,我们就能够使用组合模式。

组合模式的 UML 类图

这理解释下,Component 为容器和内容的抽象类型,除了最基本的add、remove、getChild 方法之外,还可能定义其它 operation,比如 getSize、printList等;Leaf 为叶子节点对象,该对象因为没有叶子节点,所以不能定义类似 add 这种针对容器的方法,但是可以定义叶子节点自身行为的一些方法,比如 getName 获取节点名称;Composite 为容器类,一般包含容器元素 children、操作容器的部分方法比如给容器添加元素的 add 方法。

组成角色

在组合模式中常常包含如下几个角色:

使用实例

接下来,我们通过一个实例来说明下什么是组合模式?比方拿我们以前实验室来说,说我们有很多实验部门,每个部门底下会管理多台实验设备,每个部门底下又会存在多个子部门,使用图例描述如下:

抽象构件(设备和部门的抽象):

// 抽象构件Component:可以是抽象接口也可以是抽象类
public abstract class Component {

    private String name; // 设备或部门名称

    public String getName() {
        return name;
    }

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

    public abstract void add(Component component); // 采购设备或添加子部门
    public abstract void remove(Component component); // 移除设备或子部门
    public abstract void display(int depth); // 查询该节点下所有“设备”和“部门”
}

叶子构件:

// 叶子节点Leaf类:树叶节点,模拟某个单台设备
public class Leaf extends Component{

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

    @Override
    public void add(Component component) {
        throw new UnsupportedOperationException("叶子节点(设备)不能挂载设备");
    }

    @Override
    public void remove(Component component) {
        throw new UnsupportedOperationException("叶子节点(设备)不能移除设备");
    }

    @Override
    public void display(int depth) {
        //输出树形结构的叶子节点,这里直接输出设备名称
        for(int i = 0;  i < depth; i++) {
            System.out.print("*");
        }
        System.out.println(getName());
    }
}

复合构件:

// 复合组件构建类,模拟某个组织部门
public class Composite extends Component{

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

    // 构建一个容器,用来保存该节点下所有的“设备”和“组织”
    private ArrayList<Component> componentArrayList = new ArrayList<>();

    // 采购设备时,只需要将“设备”加入到已有的“设备”列表
    @Override
    public void add(Component component) {
        this.componentArrayList.add(component);
    }

    // 移除设备时,只需要将“设备”从已有的“设备”列表中移除
    @Override
    public void remove(Component component) {
        this.componentArrayList.remove(component);
    }

    // 该容器内部,递归查询depth深度的节点下的列表内容
    @Override
    public void display(int depth) {
        // 输出树形结构(根据depth深度模拟输出多少个-)
        for (int i = 0; i < depth; i++) {
            System.out.print("*");
        }
        System.out.println(getName());
        // 递归显示
        for(Component component: componentArrayList) {
            component.display(depth + 1);
        }
    }
}

Client 角色:

// 创建根节点及其子节点
Composite root = new Composite("综合实验室");
root.add(new Leaf("综合设备1"));
root.add(new Leaf("综合设备2"));

// 创建二级节点及其子节点
Composite branchLevel21 = new Composite("化学实验室");
branchLevel21.add(new Leaf("试管"));
branchLevel21.add(new Leaf("烧杯"));
branchLevel21.add(new Leaf("锥形瓶"));
root.add(branchLevel21);

// 并列的二级节点
Composite branchLevel22 = new Composite("物理实验室");
branchLevel22.add(new Leaf("单刀单至开关设备"));
branchLevel22.add(new Leaf("电磁箱"));

Composite branchLevel221 = new Composite("精密仪器实验组");
branchLevel221.add(new Leaf("精密光学测量仪"));
branchLevel221.add(new Leaf("精密机床"));
branchLevel22.add(branchLevel221);

root.add(branchLevel22);

root.display(1);

测试输出结果如下:

可以看到,我们输出一个树形结构数据的时候并不会去关心节点的类型,容器和内容的一致性使我们能像操作一个对象一样来完成这种数据结构的输出。

总结

组合模式组合多个对象成树形结构以表示“部分-整体”关系的层次结构,使得我们能以一致的方式来处理单个对象及对象的组合,而无需关心处理的是单个对象还是某个复合对象。

本节中,我们可以看到容器和内容的一致性是如何方便快捷地创建出递归结构的 Composite 模式,最后用一张图小结下:


Author: Re:0
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Re:0 !
 Previous
装饰器模式 装饰器模式
定义本节我们要学习的设计模式叫做——装饰器模式,何为装饰器模式?
2022-03-09
Next 
Redis + Lua脚本实现服务限流 Redis + Lua脚本实现服务限流
保障服务稳定的三大利器:熔断降级、服务限流和故障模拟。今天和大家谈谈限流算法的几种实现方式,本文所说的限流并非是Nginx层面的限流,而是业务代码中的逻辑限流。
2022-03-09
  TOC