什么是桥接模式(Bridge Pattern)

桥接模式旨在将抽象部分与其实现部分分离,使它们都可以独立地变化。
想象一下遥控器(抽象部分)和电视机(实现部分):
  • 你有各种遥控器:基础遥控器、带语音功能的遥控器、带学习功能的遥控器(这些是抽象部分的不同“精化”)。
  • 你有各种品牌的电视机:索尼、三星、LG(这些是实现部分的不同“具体实现”)。
桥接模式就像是让任何一款遥控器都能控制任何一个品牌的电视机(只要它们遵循某种协议)。遥控器的功能升级(如增加新按钮)不应该强制要求电视机品牌改变,反之亦然,电视机内部技术的更新(如换屏幕技术)也不应该要求遥控器设计改变。它们通过一个“桥梁”(通常是遥控器内部持有一个对电视机操作接口的引用)连接。
桥接模式的核心在于将继承关系(可能导致类爆炸)替换为组合/聚合关系,从而分离两个独立变化的维度。

核心思想/步骤

桥接模式包含四个主要角色:
  1. 抽象 (Abstraction): 定义抽象类的接口。它包含一个对实现者 (Implementor) 对象的引用。它定义了高层逻辑,并将具体的、平台相关的或易变的操作委托给 Implementor 对象。
  1. 精化抽象 (Refined Abstraction): 扩展 Abstraction 接口,实现或覆盖其方法。它提供了更具体的抽象逻辑,但仍然通过持有的 Implementor 引用来完成底层实现。
  1. 实现者 (Implementor): 定义实现类的接口,这个接口不一定与 Abstraction 的接口完全一致。它只提供基本的操作或原语(primitive operations)。Abstraction 接口基于这些基本操作构建。
  1. 具体实现者 (Concrete Implementor): 实现 Implementor 接口,提供具体的底层操作。
关键点: Abstraction 通过组合关系(持有 Implementor 引用)而不是继承关系来连接到 Implementor。这就像一座桥,连接了抽象和实现两个独立的继承体系。

优点

  1. 分离抽象和实现 (Separation of Abstraction and Implementation): 这是桥接模式的主要目标。使两者可以独立地扩展和修改,互不影响。
  1. 提高可扩展性 (Improved Extensibility): 你可以独立地增加新的 Abstraction(或 Refined Abstraction)和新的 Implementor(或 Concrete Implementor),系统的扩展性非常好。避免了多维度继承导致的类数量爆炸问题。
  1. 符合开闭原则 (Open/Closed Principle): 对扩展开放(可以增加新的抽象和实现),对修改关闭(通常不需要修改已有的抽象和实现)。
  1. 实现细节对客户端透明 (Implementation Hiding): 客户端代码只需要与 Abstraction 交互,不需要关心底层的具体实现。

可能的缺点

  1. 增加系统复杂性: 引入了额外的类和接口(Implementor 体系),使得系统的理解和设计稍微复杂一些。
  1. 不易理解: 对于初学者来说,理解抽象和实现分离的概念以及它们如何通过桥接联系起来,可能需要一些时间。

使用场景

  1. 当你不希望在抽象和其实现之间形成固定的绑定关系时(例如,希望在运行时切换实现)。
  1. 当类的抽象以及其实现都应该可以通过生成子类的方法加以扩充时。桥接模式可以对不同的抽象接口和实现部分进行组合,并分别进行扩充。
  1. 当一个类存在两个或多个独立变化的维度,而你希望避免使用多层继承导致类数量剧增时。
  1. 跨平台应用: UI 框架(Abstraction)可能需要在不同的操作系统(Implementor)上绘制。
  1. 数据库驱动: 提供一套标准的数据库操作接口(Abstraction),可以连接到不同的数据库驱动(Implementor,如 MySQL, PostgreSQL, Oracle)。
  1. 设备驱动程序: 上层应用逻辑(Abstraction)通过标准接口与不同类型的设备驱动(Implementor)交互。
  1. 不同渲染引擎: 图形库(Abstraction)可以使用不同的渲染 API(Implementor,如 OpenGL, DirectX, Metal)。
  1. Android 中的场景:
      • 主题/皮肤引擎 (概念上): 抽象的 UI 组件(如 StyledButton, StyledText - Abstraction/Refined Abstraction)可以独立于具体的视觉主题实现(如 LightTheme, DarkTheme, CustomTheme - Implementor/Concrete Implementor)。组件的绘制逻辑委托给当前主题的实现。
      • 数据持久化层: 定义一个数据仓库接口 Repository (Abstraction),它依赖一个 DataSource 接口 (Implementor)。可以有不同的仓库实现 (UserRepository, ProductRepository - Refined Abstraction) 和不同的数据源实现 (NetworkDataSource, DatabaseDataSource, MockDataSource - Concrete Implementor)。Repository 将数据存取操作委托给注入的 DataSource。 (注意:这个场景有时也可能用策略模式或依赖注入直接实现,但桥接的思想是适用的)。
      • 跨平台框架 (如 KMM): 共享的业务逻辑或 UI 描述 (Abstraction) 可能需要调用平台特定的 API (Implementor) 来完成某些功能(如文件访问、网络请求、原生 UI 渲染)。

UML 图

下面是基于“形状 (Shape) 和 绘制 API (DrawingAPI)”示例的桥接模式 UML 图:
  • Shape (Abstraction): 定义了形状的通用接口(draw, resize),并持有一个 DrawingAPI (Implementor) 的引用。
  • Circle, Square (Refined Abstraction): 继承 Shape,实现了 draw 方法,但具体的绘制工作会委托给持有的 drawingAPI 对象。
  • DrawingAPI (Implementor): 定义了底层绘制操作的接口。
  • VectorDrawingAPI, RasterDrawingAPI (Concrete Implementor): 提供了具体的绘制实现(矢量或光栅)。
  • Client: 创建具体的 DrawingAPI 实例和具体的 Shape 实例,并将前者注入后者。然后通过 Shape 接口调用 draw。

代码示例

在这个例子中:
  • Shape 是抽象,Circle 和 Square 是精化抽象。它们负责形状的逻辑(如 resize)和调用绘制接口。
  • DrawingAPI 是实现者接口,VectorDrawingAPI 和 RasterDrawingAPI 是具体实现者,负责实际的绘制操作。
  • 关键在于 Shape 通过构造函数接收一个 DrawingAPI 实例,并将其存储起来。draw 方法的实现委托给了这个 drawingAPI 实例。
  • 客户端代码可以自由组合不同的形状和不同的绘制 API,例如创建一个使用 VectorDrawingAPI 的 Circle 和一个使用 RasterDrawingAPI 的 Square。如果需要添加新的形状(如 Triangle)或新的绘制 API(如 OpenGLAPI),可以独立进行,无需修改对方。

这个例子中,桥接的好处是什么?

在这个 "形状 (Shape) 和 绘制 API (DrawingAPI)" 的例子中,桥接模式的核心好处体现在以下几点:
  1. 分离了两个独立变化的维度:
      • 维度一:形状的种类 (Abstraction Hierarchy): 你可以有 Circle, Square, 未来可能有 Triangle, Rectangle 等。这些形状有自己的属性(如半径、边长)和一些可能的高层逻辑(如 resize)。
      • 维度二:绘制的方式 (Implementation Hierarchy): 你可以用 VectorDrawingAPI, RasterDrawingAPI, 未来可能有 OpenGLDrawingAPI, MetalDrawingAPI 等。这些 API 负责底层的实际绘制操作。
      💡
      桥接模式让这两个维度可以独立地扩展。你可以增加新的形状而不需要修改已有的绘制 API 代码,也可以增加新的绘制 API 而不需要修改形状类的代码(只要新的 API 实现了 DrawingAPI 接口定义的原语)。
  1. 避免了类爆炸 (Class Explosion):
      • 想象一下如果不使用桥接模式,而是尝试用继承来组合这两个维度。你可能需要创建 VectorCircle, RasterCircle, VectorSquare, RasterSquare... 如果再增加一个 Triangle 和一个 OpenGLAPI,你就需要再增加 VectorTriangle, RasterTriangle, OpenGLCircle, OpenGLSquare, OpenGLTriangle。类的数量会以 N x M 的方式增长(N 是形状数量,M 是 API 数量),非常难以维护。
      • 使用桥接模式后,类的数量是 N + M(Circle, Square + VectorAPI, RasterAPI),呈线性增长,扩展性大大提高。
  1. 提高了灵活性:
      • 你可以在运行时决定一个形状对象具体使用哪个 DrawingAPI 来绘制,只需要在创建 Shape 对象时传入不同的 DrawingAPI 实例即可。例如,同一个 Circle 的定义,可以轻易地切换绘制方式。
  1. 实现细节对客户透明:
      • 客户端代码(如 main 函数)只需要与 Shape 抽象层交互 (shape.draw()),不需要关心它内部是用哪个 DrawingAPI 实现的。

总结

  • 桥接模式通过将抽象部分与实现部分分离,使用组合/聚合关系替换继承关系,来处理多维度变化的问题。
  • 它极大地提高了系统的灵活性和可扩展性,允许两个维度独立地演进。
  • 虽然会增加一些类的数量,但在需要解耦抽象和实现,或者避免类爆炸的场景下非常有用。
💡
记住它的核心:抽象持有实现的引用,继承变组合,两套体系独立飞翔。
 
设计模式——结构型-外观模式设计模式——结构型-组合模式