组合模式

203 阅读2分钟

组合模式是一种结构型设计模式,可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用组合在一起的各个对象,且使用组合体和使用各对象是一样的效果;

模式特点:
  1. 树叶对象对外接口保存一致(操作与数据结构一致)
  2. 调用顶层对象,会自行遍历其下的叶对象执行
使用案例:

下面我们用组合模式来实现一个常见的例子,文件夹和文件之间的关系,非常适合用组合模式来描述。文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组合成一棵树。

// 树对象 - 文件夹
class CFolder {
    constructor(name) {
        this.name = name;
        this.files = [];
    }
    add(file) {
        this.files.push(file);
    }
    scan() {
        for (let file of this.files) {
            file.scan();
        }
    }
}

// 叶对象 - 文件
class CFile {
    constructor(name) {
        this.name = name;
    }
    add(file) {
        throw new Error('文件下面不能再添加文件');
    }
    scan() {
        console.log(`开始扫描文件:${this.name}`);
    }
}

let mediaFolder = new CFolder('娱乐');
let movieFolder = new CFolder('电影');
let musicFolder = new CFolder('音乐');

let file1 = new CFile('钢铁侠.mp4');
let file2 = new CFile('再谈记忆.mp3');
movieFolder.add(file1);
musicFolder.add(file2);
mediaFolder.add(movieFolder);
mediaFolder.add(musicFolder);
mediaFolder.scan();
优缺点:

优点:

  • 可以利用多态和递归机制更方便的使用复杂树结构

  • 解耦调用者与复杂元素之间的联系,处理方式变得简单

  • 满足开闭原则:

缺点:包裹对象创建太多,额外增加内存负担

总结:只要是树形结构或者只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,便可考虑使用组合模式。
一些需要注意的地方
  • 组合模式是聚合关系,而不是父子关系,因为叶对象(最开始的单个命令)不是组合对象的子类。组合对象可以把请求委托给它的所有叶对象,因为它们有相同的接口(和dom树很像)。
  • 组合模式的应用场景:必须要每个节点都有相同的接口,以及操作一致性。比如之前的例子,现在每个文件节点都有scan方法,但是如果有的文件有删除方法,有的没有,那么组合模式就不适用,要么都有,那么都没有。
  • 双向映射关系:一个节点只能属于一个组合对象,不能同时属于两个,比如一个文件,只能有一个直接的文件夹来包含,不可能同时有两个。如果一个人属于开发组,同时又属于测试组,这种交叉情况就不适用于组合模式。
  • 组合模式的对象关系和职责链模式很像。