桥接模式: Bridge

2,621 阅读5分钟

简介

桥接模式是结构型模式 (Structural Patterns) 的一种.

Making even a simple change to a monolithic codebase is pretty hard because you must understand the entire thing very well. Making changes to smaller, well-defined modules is much easier.

如果软件系统中某个类存在多个独立变化的维度, 通过该模式可以将这多个维度分离出来, 使他们可以独立扩展, 让系统更加符合"单一职责原则 (SRP)". 与多层继承方案不同, 它将多个独立变化的维度设计为多个独立的继承等级结构, 并且在抽象层建立一个抽象关联, 该关联关系类似一条连接多个独立继承结构的桥, 故名桥接模式.

桥接模式用一种巧妙的方式处理多层继承存在的问题, 用抽象关联取代了多层继承,将类之间的静态继承关系转换为动态的对象组合关系, 使得系统更加灵活, 并易于扩展, 同时有效控制了系统中类的个数.

简单的例子

interface Shape {
    val shapeType: ShapeType
}

abstract class Circle : Shape {
    val shapeType = TypeCircle
    abstract val shapeColor: ShapeColor
    ...
}

abstract class Square : Shape {
    val shapeType = TypeSquare
    abstract val shapeColor: ShapeColor
    ...
}

class RedCircle : Circle() {...}
class RedSquare : Square() {...}
class BlueCircle : Circle() {...}
class BlueSquare : Square() {...}
...

在这个例子中, 无论增加形状, 还是增加颜色, 都会导致子类数量快速增加. 解决方案是划分成两个独立变化的维度: Type 和 Color.

abstract Shape(val shapeColor: ShapeColor) {
    abstract val shapeType: ShapeType
}

class Circle(val shapeColor: ShapeColor) : Shape(ShapeColor) {
    val shapeType = TypeCircle
    ...
}

class Square(val shapeColor: ShapeColor) : Shape(ShapeColor) {
    val shapeType = TypeSquare
    ...
}

现在, 增加颜色不会再导致子类数量增加了. Shape 和 Color 直接的关系就是那座桥.

基本概念

在 GoF 中, 桥接模式的定义如下:

  1. Abstraction:拥有一个 Implementation 类型的对象引用
  2. Implementation:Implementation 和 Abstraction 接口并不一定完全一致,实际上这两个接口可以完全不一样, Implementation 提供具体操作方法,而 Abstraction 提供更高层次的调用
  3. Concrete Implementation:实现 Implementation 接口,给出具体实现
  4. Refined Abstraction:扩展 Abstraction 中的接口定义
  5. Client: 连接 Abstraction 和 Implementation

真实的例子

伪代码见: refactoring.guru/design-patt…

遥控器的基类 (Remote) 中包含一个设备的引用 (device), 所有的遥控器都可以通过通用的设备接口 (Device interface) 控制设备.

  • 通用设备接口使得遥控器代码可以复用于遥控多种不同的设备 (Radio / TV).
  • 通过继承遥控器基类, 可以实现只有两个按钮的简单遥控器, 或是带触摸屏的复杂遥控器.

其它真实的例子

- 驱动程序 / JDBC

应用程序使用驱动程序的方式是桥接模式的一个常见例子.

外设驱动按照制定好的接口操作外设, 使用该驱动的应用是一个 Abstraction, 它的运行结果取决于它使用的是哪一个外设+驱动. 每个驱动程序都是适配器模式 (Adapter) 的一个例子, 而使用驱动程序的应用是桥接模式的一个例子. 桥接模式将应用的开发和驱动的开发分离开来.

JDBC 是用于执行 SQL 的 Java 接口, JDBC 驱动就是实现了该接口的类. 任何一个数据库只要提供了 JDBC 驱动, Java 的数据库应用程序就可以操作它. JDBC 的这种架构将 Abstraction 和 Implementation 相分离, 使得数据库应用和数据库能够独立的发展, 是 Bridge 模式的一个极好的例子.

- VCS (Version Control System)

IDEA 的 VSC, 或者 Sourcetree 之类的软件, 都可以在同样一套 UI 下, 用同样的概念 (比如 History, Diff) 操作不同的版本控制系统 (比如 Git, Svn).

应用场景

当一个应用/类的某个功能要支持多个变体(比如支持多个数据源)

一个应用/类越大, 弄清楚或改动的的代价就越大. 对于一个变体的改动可能会导致整个应用/类内的大量改动, 这经常会导致各种问题. Bridge 模式可以把应用/类拆分成多个独立的结构, 之后的改动就在各自的结构中, 让代码的改动影响最小化.

当一个类在多个正交的维度上可扩展时

Bridge 模式建议将每个维度抽象成独立的继承关系, 这样之前的类可以将相关的功能和扩展代理给对应的维度去做, 而不是完全自己做.

如果你希望支持运行时切换实现 (Implementation)

虽然不是最重要的, 但 Bridge 模式允许你切换 Abstraction 中引用的 Implementation.

这可能是 Bridge 模式容易和策略模式 (Strategy) 混淆的一个原因. 注意: 设计模式呈现的不仅是最终的代码, 还有解决问题的思路.

优缺点

优点

  • 可以用来创建跨平台的应用.
  • Client 和 Abstraction 交互, 不暴露 Implementation 的细节.
  • 开闭原则: 可以分别增加 Abstraction 和 Implementation 的子类而不相互影响.
  • 单一职责原则: Abstraction 或 Implementation 的开发人员可以 Focus 各自的代码.

缺点

  • 如果一个类的功能之间功能之间相关性较强, 使用 Bridge 模式可能会让代码变得更复杂.

和其它设计模式的关系

  • Bridge, State, Strategy (Adapter 在某种程度上也算) 有一些相似, 这些设计模式都采用了将功能代理给其它类去做的方式. 但是, 他们解决的问题并不相同. 设计模式呈现的不仅是最终的代码, 还有解决问题的思路.
  • Abstract Factory 可以和 Bridge 一起使用, 通常用于 Abstraction 只能使用特定的 Implementation 的情况. 在这种情况下, 可以用 Abstract Factory 封装 Client 代码中建立 Abstraction 和 Implementation 连接的部分.
  • Builder 可以和 Bridge 一起使用, Director 作为 Abstraction, 多个 Builder 作为 Implementation.

@Uraka.Lee