# Decorator（装饰器模式）

Decorator（装饰器模式）属于结构型模式，是一种拓展对象额外功能的设计模式，别名 `wrapper`。

**意图：动态地给一个对象添加一些额外的职责。就增加功能来说，Decorator 模式相比生成子类更为灵活。**

## 举例子

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

### 相框

照片 + 相框 = 带相框的照片，这背后就是一种装饰器模式：照片具有看的功能，相框具有装饰功能，在你看照片的基础上，还能看到精心设计的相框，增加了美感，同时相框还可以增加照片的保存时间与安全性。

相框与照片是一种组合关系，任何照片都可以放到相框中，而不是每个照片生成一个特定的相框，显然，组合的方式更加灵活。

### 带有缓存的文件读写

假设我们有一个类 `FileIO` 用来读写文件，但是没有缓存能力，此时是新建一个 `CachedFileIO` 子类好，还是创建一个 `CachedIO`?

一眼看上去好像 `CachedFileIO` 用起来更方便，而 `CachedIO` 的用法是 `new CachedIO(new FileIO())` 稍微麻烦一些，但如果我们增加一个网络读写类 `NetworkIO`，一个数据库读写类 `DBIO` 呢？

显然，继承的方式会使子类数量极速膨胀，而组合的方式则非常灵活，生成一个支持缓存的网络读写器，只需要 `new CachedIO(new NetworkIO())` 即可，这就是组合灵活的地方。

当然，为了实现这个能力，`CachedIO` 需要与 `FileIO`、`CachedFileIO`、`CachedIO` 继承自同一个类，具备相同的接口。

### 搭建平台的组件 wrapper

装饰器模式别名也叫 `wrapper`，`wrapper` 也经常在前端搭建场景中遇到，当搭建平台加载一个组件时，希望拓展其基础能力，一般会使用 `wrapper` 层对组件进行嵌套，`wrapper` 层就是在不改变 API 的基础上，对第三方组件进行增强。

## 意图解释

**意图：动态地给一个对象添加一些额外的职责。就增加功能来说，Decorator 模式相比生成子类更为灵活。**

不同于继承，组合可以在运行时进行，所以称之为 “动态添加”，这里的 “额外职责” 泛指一切功能，比如在按钮点击时进行一些 log 日志的打印，在绘制 text 文本框时，额外绘制一个滚动条和边框等等。

“就增加功能来说，Decorator 模式相比生成子类更为灵活” 这句话的含义是，组合比继承更灵活，当可拓展的功能很多时，继承方案会产生大量的子类，而组合可以提前写好处理函数，在需要时动态构造，显然是更灵活的。

## 结构图

<img width=600 src="https://img.alicdn.com/tfs/TB1cmhe3FY7gK0jSZKzXXaikpXa-1624-688.png">

`ConcreteComponent` 指的是需要被装饰的组件，可以看到，装饰器 `Decorator` 与他都继承同一个类，这样能保证 API 的一致，才保证无论装饰多少层，始终符合 `Component` 类型。

装饰器如果有多种，就要将 `Decorator` 申明为抽象类，`ConcreteDecoratorA`、`ConcreteDecoratorB` 分别实现它们，如果只有一种装饰器，可以退化到 `Decorator` 自身就是一种实现。

## 代码例子

下面例子使用 typescript 编写。

```typescript
class Component {
  // 具有点击事件
  public onClick = () => {}
}

class Decorator extends Component {
  private _component

  constructor(component) {
    this._component = component
  }

  public onClick = () => {
    log('打点')
    this._component.onClick()
  }
}

const component = new Component()
// 一个普通的点击
component.onClick()

const wrapperComponent = new Decorator(component)
// 一个具有打点功能的点击
wrapperComponent.onClick()
```

其实方法很简单，通过组合，我们得到了一个能力更强的组件，而实现的方式就是利用构造函数保存组件实例，并在复写函数时，增加一些增强实现。

## 弊端

装饰器的问题也是组合的问题，过多的组合会导致：

- 组合过程的复杂，要生成过多的对象。
- 包装器层次增多，会增加调试成本，我们比较难追溯到一个 bug 是在哪一层包装导致的。

## 总结

装饰器模式是非常常用的模式，Decorator 是一个透明的包装，只要保证包装的透明性，就可以最大限度发挥装饰器模式的优势。

最后总结一个装饰器应用图：

<img width=500 src="https://img.alicdn.com/tfs/TB1wlpgqPMZ7e4jSZFOXXX7epXa-1232-478.png">

> 讨论地址是：[精读《设计模式 - Decorator 装饰器模式》· Issue #286 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/286)

**如果你想参与讨论，请 [点击这里](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)）
