设计模式(七)—— 组合

142 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情

概念

组合模式(Composite)是针对由多个节点对象(部分)组成的 树形结构 的对象(整体)而发展出的一种 结构型 设计模式,它能够使客户端在操作整体对象或者其下的每个节点对象时做出统一的响应,保证树形结构对象是用方法的一致性,使客户端不必关注对象的整体或部分,最终达到对象复杂的层级结构与客户端解耦的目的。

实例演示

注:本文内容参考 《秒懂设计模式》一书,本文对其做了概括凝练,主要是为了自身学习使用,如果无法理解,建议查看原书。

日常工作中,其实我们会经常遇到树形结构展示的 case,电商系统的类目概念就是一个很好的例子。分为一级类目,二级类目、三级类目等等,类目之间是父子关系,他们最大的区分是这个类目是否是叶子类目。最终展示出来就是一个树状结构。

日常生活中,文件系统也是一个很好的例子。文件夹中可以嵌套创建文件夹,也可以创建文件。下面我们就以文件系统来举例介绍一下组合模式。

1. 定义节点

首先我们需要先定义一个抽象节点,此类肩负着确立树形结构的重任,这也是组合模式数据结构的精髓所在。因为文件和文件夹必须包含名字,并且创建节点时就需要传入,所以我们在初始化时就传进去。

接下来我们再定义一个添加节点的方法,目前节点可能会包含下个节点,也有可能没有,所以我们定义一个抽象方法 add(当然还有其他操作,这里我们简化),具体代码如下:

public abstract class Node {
    protected String name;//节点命名

    public Node(String name) {//构造方法需传入节点名
        this.name = name;
    }

    //添加下级子节点方法
    protected abstract void add(Node child);
}

2. 实现类

接下来我们要来实现这个类。

首先是文件夹文件夹类继承了抽象节点类 Node,文件夹节点中定义了一个次级节点列表 List<Node>,即文件夹下级可以包含任意多个文件夹或者文件。

同时文件夹因为可以继续添加文件或文件夹,所以实现了 add 方法,代码如下所示:

public class Folder extends Node {
    //文件夹可以包含子节点(子文件夹或者文件)
    private List<Node> childrenNodes = new ArrayList<>();

    public Folder(String name) {
        super(name);//调用父类的构造方法
    }

    @Override
    protected void add(Node child) {
        childrenNodes.add(child);//可以添加子节点
    }
}

而文件节点调用 add 方法时则会抛错,所以代码中直接显示“不能添加子节点”。

public class File extends Node{

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

    @Override
    protected void add(Node child) {
        System.out.println("不能添加子节点。");
    }
}

3. 客户端调用

客户端调用就很简单了,我们来看下:

public class Client {
    public static void main(String[] args) {
        Node driveD = new Folder("D盘");

        Node doc = new Folder("文档");
        doc.add(new File("简历.doc"));
        doc.add(new File("项目介绍.ppt"));

        driveD.add(doc);

        Node music = new Folder("音乐");

        Node jay = new Folder("周杰伦");
        jay.add(new File("双截棍.mp3"));
        jay.add(new File("告白气球.mp3"));
        jay.add(new File("听妈妈的话.mp3"));

        Node jack = new Folder("张学友");
        jack.add(new File("吻别.mp3"));
        jack.add(new File("一千个伤心的理由.mp3"));

        music.add(jay);
        music.add(jack);

        driveD.add(music);
    }
}

4. 树形展示

当然我们还可以做的更高级一些,比如我们最后展示出来的是一个树状的结构,那么我们在抽象类就定义一个 tree 方法。这个 tree 方法里面根据输入的数字不同,输出不同个数的空格。

public abstract class Node {
    protected String name;//节点命名

    public Node(String name) {//构造方法需传入节点名
        this.name = name;
    }

    //添加下级子节点方法
    protected abstract void add(Node child);

    protected void tree(int space){
        for (int i = 0; i < space; i++) {
            System.out.print("  ");//先循环输出space个空格
        }
        System.out.println(name);//接着再输出自己的名字
    }
}

总结

组合合适适合带有重复结构,并且相互嵌套的场景。

组合模式的各角色定义如下:

  • Component(组件接口):所有复合节点与叶节点的高层抽象,定义出需要对组件操作的接口标准。对应本章例程中的抽象节点类,具体使用接口还是抽象类需根据具体场景而定。
  • Composite(复合组件):包含多个子组件对象(可以是复合组件或叶端组件)的复合型组件,并实现组件接口中定义的操作方法。对应本章例程中作为“根节点/枝节点”的文件夹类。
  • Leaf(叶端组件):不包含子组件的终端组件,同样实现组件接口中定义的操作方法。对应本章例程中作为“叶节点”的文件类。
  • Client(客户端):按所需的层级关系部署相关对象并操作组件接口所定义的接口,即可遍历树结构上的所有组件。

参考文档

  • 《秒懂设计模式》—— 刘韬