问题画像

  1. tree 结构;
  2. 主要分为 组合节点 与 叶子节点;
  3. 组合节点 存在一个 children 字段,用于保存其他 组合节点 与 叶子节点;
  4. 用户可以使用一套 api 去操作这两个节点。

比如: 文件系统和公司的组织架构图;对于文件系统来说,目录和文件,这两者整体可以使用一套 api,方便用户使用和理解,而对于其中的差异,可以单独处理。

定义

组合模式 允许以相同的方式处理单个对象和复合对象,意图是将对象组合成树形结构以表示 “部分 - 整体” 的层次结构;

组合模式 的核心是简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。

总体结构:

  1. 一个抽象类,也是暴露给外界的 api;
  2. 单个对象,也就是叶子(Leaf)节点,需要继承抽象类;
  3. 复合对象,也就是容器组件(Composite),需要继承抽象类,区别于 叶子节点 的是,存在一个 children 属性,用于保存叶子节点以及其他容器组件;

实现

当前场景是一个简单的文件系统,属于树形结构,实现了添加文件到目录,已经获取当前目录下有多少文件的功能。

abstract class FileComponent {
  isFile: boolean;
  fileName: string;
  constructor(fileName: string, isFile: boolean) {
    this.isFile = isFile;
    this.fileName = fileName;
  }
  abstract add(file: FileLeaf | Folder): FileComponent;
  abstract getFileCount(): number;
}

// 组合节点
class Folder extends FileComponent {
  // 这里是比较核心的点,树结构用来
  list: FileLeaf[] = [];
  constructor(fileName: string, isFile: boolean) {
    super(fileName, isFile);
  }

  add(file: FileLeaf): FileComponent {
    const result = this.list.some((item) => item.fileName === file.fileName);
    if (result) return this;
    this.list.push(file);
    return this;
  }
  getFileCount() {
    const fileCount = this.list.reduce((accumulator, item) => {
      if (item.isFile) return accumulator + 1;
      return accumulator;
    }, 0);

    return fileCount;
  }
}

// 叶子节点
class FileLeaf extends FileComponent {
  constructor(fileName: string, isFile: boolean) {
    super(fileName, isFile);
  }
  add(): FileLeaf {
    throw new Error("文件不可执行该操作。");
  }
  getFileCount(): number {
    return 1;
  }
}

// 创建一个根目录
const root = new Folder('root', false);
// 创建两个目录
const folder = new Folder('folder', false);
// 创建三个文件
const file1 = new FileLeaf('file1', true);
const file2 = new FileLeaf('file2', true);
const file3 = new FileLeaf('file3', true);

folder.add(file3);

root.add(file1)
  .add(file2)
  .add(folder);

console.log(root.getFileCount()); // 2
console.log(folder.getFileCount()); // 1

总结

  1. 主要优点有:
  • 客户端可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端操作;
  • 更容易组合整个组合体,不需要修改源代码;
  1. 其主要缺点是:
  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  • 不容易处理容器中比较特殊的点,缺少普遍性。

更多推荐