From e72318adb46fed5dbdb8920680a3400a931cca0e Mon Sep 17 00:00:00 2001 From: ascoders <576625322@qq.com> Date: Sun, 15 Nov 2020 16:37:09 +0800 Subject: [PATCH] 173 --- ...45\346\250\241\345\274\217\343\200\213.md" | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 "173.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Bridge \346\241\245\346\216\245\346\250\241\345\274\217\343\200\213.md" diff --git "a/173.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Bridge \346\241\245\346\216\245\346\250\241\345\274\217\343\200\213.md" "b/173.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Bridge \346\241\245\346\216\245\346\250\241\345\274\217\343\200\213.md" new file mode 100644 index 00000000..830df814 --- /dev/null +++ "b/173.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Bridge \346\241\245\346\216\245\346\250\241\345\274\217\343\200\213.md" @@ -0,0 +1,111 @@ +# 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 能力隔离、将组件功能与平台功能隔离,只有做到了抽象部分与实现部分的隔离,才可以通过组合满足更多场景。 + +## 结构图 + + + +- 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` 也可以利用其基础能力画线了。 + +## 弊端 + +不要过度抽象,桥接模式是为了让类的职责更单一,维护更便捷,但如果只是个小型项目,桥接模式会增加架构设计的复杂度,而且不正确的模块拆分,把本来关联的逻辑强制解耦,在未来会导致更大的问题。 + +另外桥接模式也有简单与复杂模式之分,只有一种实现的场景就不要用抽象工厂做过度封装了。 + +## 总结 + +桥接模式让我们重新审视类的设计是否合理,把类中不相关,或者说相互独立的维度抽出去,由桥接模式做桥接的方式使用,这样会使每个类功能更内聚,代码量更少更清晰,组合能力更强大,更容易做拓展。 + +下图做了一个简单的解释: + + + +> 讨论地址是:[精读《设计模式 - Bridge 桥接模式》· Issue #280 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/280) + +**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** + +> 关注 **前端精读微信公众号** + + + +> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))