# Composite（组合模式）

Composite（组合模式）属于结构型模式，是一种统一管理树形结构的抽象方式。

**意图：将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。**

## 举例子

如果看不懂上面的意图介绍，没有关系，设计模式需要在日常工作里用起来，结合例子可以加深你的理解，下面我准备了三个例子，让你体会什么场景下会用到这种设计模式。

### 公司组织关系树

公司组织关系可能分为部门与人，其中人属于部门，有的人有下属，有的人没有下属。如果我们统一将部门、人抽象为组织节点，就可以方便的统计某个部门下有多少人、财务数据等等，而不用关心当前节点是部门还是人。

### 操作系统的文件夹与文件

操作系统的文件夹与文件也是典型的树状结构，为了方便递归出文件夹内文件数量或者文件总大小，我们最好设计的时候就将文件夹与文件抽象为文件，这样每个节点都拥有相同的方法添加、删除、查找子元素，而不需要关心当前节点是文件夹或是文件。

### 搭建平台的组件与容器

容器与组件的关系很小，用户常常认为容器也是一种组件，但搭建平台实现时，容器与组件稍有不同，不同之处在于容器可以嵌套子元素，而组件不可以。如果因此搭建平台就将组件分为容器与组件，会导致 API 割裂为两套，不利于组件开发者维护与用户理解，比较好的设计思路是将组件与容器统一看成组件，组件只是一种没有子元素的特殊容器，这样组件与容器就可以拥有相同的 API，统一理解与操作了。

## 意图解释

**意图：将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。**

比较好理解，组合是指多个对象虽然有一定差异，但共同组合成了一个树形结构，那么对象之间就一定存在 “部分 - 整体” 的关系，组合模式要求我们抽象一个对象 `Component` 作为统一操作模型，叶子结点与非叶子结点都实现了所有功能，即便是没有子元素的叶子结点，为了强调透明性，还是具备比如 `getChildren` 方法，只不过永远都返回 `null`。

## 结构图

<img width=600 src="https://img.alicdn.com/tfs/TB19t0j27Y2gK0jSZFgXXc5OFXa-1504-678.png">

其中 `Component` 是组合中对象声明接口，一般会实现所有公共类的所有接口，还要提供一个接口管理其子组件。

`Leaf` 表示叶子结点，没有子结点，相应的 `Composite` 就是有子结点的节点。

可以看到，组合模式就是将树状结构中所有节点统一抽象了，**我们不需要关心叶子结点与非叶子结点的差异，而可以通过组合模式的抽象屏蔽掉这些差异，统一处理。**

## 代码例子

下面例子使用 typescript 编写。

```typescript
// 统一的抽象
class Component {
  // 添加子元素
  public add() {}
  // 获取名称
  public getName() {}
  // 获取子元素
  public getChildren() {}
}

// 非叶子结点
class Composite extends Component {
  public add(component: Component) {
    this.children.push(component)
  }

  public getName() {
    return this.name
  }

  public getChildren() {
    return this.children
  }
}

// 叶子结点
class Leaf extends Component {
  public add(component: Component) {
    throw Error('叶子结点无法添加元素')
  }

  public getName() {
    return this.name
  }

  public getChildren() {
    return null
  }
}
```

最后我们把对所有节点的操作都转为 `Component` 对象，而不用关心这个对象具体是 `Composite` 或 `Leaf`。

## 弊端

组合模式进行了一层抽象，其实增加了复杂系统中业务复杂度。如果 `Composite` 与 `Leaf` 差异过大，那么统一抽象带来的理解成本是很高的。

同时，`Leaf` 不得不实现一些仅 `Composite` 存在的空函数，比如 `add` `delete`，即便这些方法对他们是无意义的，此时可能要进行统一的无效或错误处理，才能使业务层真正不用感知他们的区别，否则 `add` 可能会失败，其本质上还是将节点的区别暴露给了业务层。

## 总结

组合模式是针对树状结构这个特定场景的统一抽象方案，对降低系统复杂度有很重要的意义，同时也不要忘了过度抽象是有害的，我们要拿捏其中的度。

下图做了一个简单的解释：

<img width=500 src="https://img.alicdn.com/tfs/TB1_g24rvzO3e4jSZFxXXaP_FXa-1228-614.png">

程序中始终关注 `Component` 就行了，树状结构的差异已经被抹平。

> 讨论地址是：[精读《设计模式 - Composite 组合模式》· Issue #284 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/284)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
