# Bridge（桥接模式）

Bridge（桥接模式）属于结构型模式，是一种解决继承后灵活拓展的方案。

**意图：将抽象部分与它的实现部分分离，使它们可以独立地变化。**

桥接模式比较难理解，我会一步步还原该设计模式的思考，让你体会这个设计模式是如何一步一步被提炼出来的。

## 举例子

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

### 汽车生产线改造为新能源生产线

汽油车与新能源汽车的生产流程有很大相似之处，那么汽油车生产线能否快速改造为新能源汽车生产线呢？

如果汽油车生产线没有将内部实现解耦，只把生产汽油车的各部分独立了出来，对新能源车生产线是没什么用处的，但如果汽油车生产线提供了更底层的能力，比如加装轮胎，加装方向盘，那么这些步骤是可以同时被汽油车与新能源车所共享的。

在设计汽油车生产线时，就将生产过程与汽油车解耦，使其可以快速运用到新能源汽车的生产，这就是桥接模式的一种运用。

### 窗口（Window）类的派生

假设存在一个 Window 窗口类，其底层实现在不同操作系统是不一样的，假设对于操作系统 A 与 B，分别有 AWindow 与 BWindow 继承自 Window，现在要做一个新功能 ManageWindow（管理器窗口），就要针对操作系统 A 与 B 分别生成 AManageWindow 与 BManageWindow，这样显然不容易拓展。

无论我们新增支持 C 操作系统，还是新增支持一个 IconWindow，类的数量都会成倍提升，因为我们所做的 AMangeWindow 与 BMangeWindow 同时存在两个即以上的独立维度，这使得增加维度时，代码变得很冗余。

### 适配多个搭建平台的物料

做前端搭建平台时，经常出现一些物料（组件）因为固化了某个搭建平台的 API，因此无法迁移到另一个搭建平台，如果要迁移，就需要为不同的平台写不同的组件，而这些组件中大部分 UI 逻辑都是一样的，这使得产生大量代码冗余，如果再兼容一个新搭建平台，或者为已有的 10 个搭建平台再创建一个新组件，工作量都是写一个组件的好几倍。

## 意图解释

**意图：将抽象部分与它的实现部分分离，使它们可以独立地变化。**

“抽象” 部分与 “实现” 部分分离，这句话看起来很像接口与实现。确实，如果 “抽象” 指的是 接口(Interface)，而 “实现” 指的是 类(Class) 的话，这就是简简单单的 `class MyWindow implements Window` 类实现过程而已。

但后半句话 “使它们可以独立地变化” 会让你难以和前半句联系起来，如果说 “抽象” 不变，“实现” 可以随意改变还好理解，但反过来就难以解释了。

**其实桥接模式中，抽象指的是一种接口（Abstraction），实现指的也是一种接口（Implementor），其中 Implementor 并不是直接实现了 Abstraction 定义的接口，而是提供更底层的方法，使 Abstraction 可以基于它们封装出自己的接口实现。**

这样一来，Abstraction 的接口可以随意变化，毕竟调用的是 Implementor 提供函数的组合，只要 Implementor 提供的功能全面，Implementor 可以不变；相应的，Implementor 的实现也可以随意变化，只要提供的底层函数不变，就不影响 Abstraction 对其的使用。

上面举的三个例子都是这样，我们应该把汽油车生产线的标准与通用汽车生产线标准分离、将具体功能窗口与适配不同操作系统的基础 GUI 能力隔离、将组件功能与平台功能隔离，只有做到了抽象部分与实现部分的隔离，才可以通过组合满足更多场景。

## 结构图

<img width=600 src="https://img.alicdn.com/tfs/TB1mZv52oH1gK0jSZSyXXXtlpXa-1726-696.png">

- Abstraction：定义抽象类的接口。
- RefinedAbstraction：扩充 Abstraction。
- Implementor：定义实现类的接口，该接口可以与 Abstraction 接口不一致。
- ConcreteImplementor：实现 Implementor 接口并定义它的具体实现。

抽象部分就是 Abstraction，实现部分就是 Implementor，在这个结构图中，它们是分离的，可以各自独立变化的，桥接模式，就是指 `imp` 这个桥，通过 Implementor 实现 Abstraction 接口，就算是桥接上了，这种组合的桥接相比普通的类实现更灵活，更具有拓展性。

## 代码例子

对于完全版桥接模式，Implementor 可以有多套实现，Abstraction 不需关心具体用的是哪一种实现，而是通过抽象工厂方式封装。下面举一个简单版的例子。

下面例子使用 typescript 编写。

```typescript
class Window {
  private windowImp: WindowImp

  public drawBox() {
    // 通过画线生成 box
    this.windowImp.drawLine(0, 1)
    this.windowImp.drawLine(1, 1)
    this.windowImp.drawLine(1, 0)
    this.windowImp.drawLine(0, 0)
  }
}

// 拓展 window 就非常容易
class SuperWindow extends Window {
  public drawIcon {
    // 通过自定义画线
    this.windowImp.drawLine(0, 5)
    this.windowImp.drawLine(3, 9)
  }
}
```

桥接模式的精髓，通过上面的例子可以这么理解：

`Window` 的能力是 `drawBox`，那继承 `Window` 容易拓展 `drawIcon` 吗？默认是不行的，因为 `Window` 并没有提供这个能力。经分析可以看出，划线是一种基础能力，不应该与 `Window` 代码耦合，因此我们将基础能力放到 `windowImp` 中，这样 `drawIcon` 也可以利用其基础能力画线了。

## 弊端

不要过度抽象，桥接模式是为了让类的职责更单一，维护更便捷，但如果只是个小型项目，桥接模式会增加架构设计的复杂度，而且不正确的模块拆分，把本来关联的逻辑强制解耦，在未来会导致更大的问题。

另外桥接模式也有简单与复杂模式之分，只有一种实现的场景就不要用抽象工厂做过度封装了。

## 总结

桥接模式让我们重新审视类的设计是否合理，把类中不相关，或者说相互独立的维度抽出去，由桥接模式做桥接的方式使用，这样会使每个类功能更内聚，代码量更少更清晰，组合能力更强大，更容易做拓展。

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

<img width=500 src="https://img.alicdn.com/tfs/TB1nossndTfau8jSZFwXXX1mVXa-1308-1078.png">

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

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