什么是桥接模式(Bridge Pattern)
桥接模式旨在将抽象部分与其实现部分分离,使它们都可以独立地变化。
想象一下遥控器(抽象部分)和电视机(实现部分):
- 你有各种遥控器:基础遥控器、带语音功能的遥控器、带学习功能的遥控器(这些是抽象部分的不同“精化”)。
- 你有各种品牌的电视机:索尼、三星、LG(这些是实现部分的不同“具体实现”)。
桥接模式就像是让任何一款遥控器都能控制任何一个品牌的电视机(只要它们遵循某种协议)。遥控器的功能升级(如增加新按钮)不应该强制要求电视机品牌改变,反之亦然,电视机内部技术的更新(如换屏幕技术)也不应该要求遥控器设计改变。它们通过一个“桥梁”(通常是遥控器内部持有一个对电视机操作接口的引用)连接。
桥接模式的核心在于将继承关系(可能导致类爆炸)替换为组合/聚合关系,从而分离两个独立变化的维度。
核心思想/步骤
桥接模式包含四个主要角色:
- 抽象 (Abstraction): 定义抽象类的接口。它包含一个对实现者 (Implementor) 对象的引用。它定义了高层逻辑,并将具体的、平台相关的或易变的操作委托给 Implementor 对象。
- 精化抽象 (Refined Abstraction): 扩展 Abstraction 接口,实现或覆盖其方法。它提供了更具体的抽象逻辑,但仍然通过持有的 Implementor 引用来完成底层实现。
- 实现者 (Implementor): 定义实现类的接口,这个接口不一定与 Abstraction 的接口完全一致。它只提供基本的操作或原语(primitive operations)。Abstraction 接口基于这些基本操作构建。
- 具体实现者 (Concrete Implementor): 实现 Implementor 接口,提供具体的底层操作。
关键点: Abstraction 通过组合关系(持有 Implementor 引用)而不是继承关系来连接到 Implementor。这就像一座桥,连接了抽象和实现两个独立的继承体系。
优点
- 分离抽象和实现 (Separation of Abstraction and Implementation): 这是桥接模式的主要目标。使两者可以独立地扩展和修改,互不影响。
- 提高可扩展性 (Improved Extensibility): 你可以独立地增加新的 Abstraction(或 Refined Abstraction)和新的 Implementor(或 Concrete Implementor),系统的扩展性非常好。避免了多维度继承导致的类数量爆炸问题。
- 符合开闭原则 (Open/Closed Principle): 对扩展开放(可以增加新的抽象和实现),对修改关闭(通常不需要修改已有的抽象和实现)。
- 实现细节对客户端透明 (Implementation Hiding): 客户端代码只需要与 Abstraction 交互,不需要关心底层的具体实现。
可能的缺点
- 增加系统复杂性: 引入了额外的类和接口(Implementor 体系),使得系统的理解和设计稍微复杂一些。
- 不易理解: 对于初学者来说,理解抽象和实现分离的概念以及它们如何通过桥接联系起来,可能需要一些时间。
使用场景
- 当你不希望在抽象和其实现之间形成固定的绑定关系时(例如,希望在运行时切换实现)。
- 当类的抽象以及其实现都应该可以通过生成子类的方法加以扩充时。桥接模式可以对不同的抽象接口和实现部分进行组合,并分别进行扩充。
- 当一个类存在两个或多个独立变化的维度,而你希望避免使用多层继承导致类数量剧增时。
- 跨平台应用: UI 框架(Abstraction)可能需要在不同的操作系统(Implementor)上绘制。
- 数据库驱动: 提供一套标准的数据库操作接口(Abstraction),可以连接到不同的数据库驱动(Implementor,如 MySQL, PostgreSQL, Oracle)。
- 设备驱动程序: 上层应用逻辑(Abstraction)通过标准接口与不同类型的设备驱动(Implementor)交互。
- 不同渲染引擎: 图形库(Abstraction)可以使用不同的渲染 API(Implementor,如 OpenGL, DirectX, Metal)。
- 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)" 的例子中,桥接模式的核心好处体现在以下几点:
- 分离了两个独立变化的维度:
- 维度一:形状的种类 (Abstraction Hierarchy): 你可以有 Circle, Square, 未来可能有 Triangle, Rectangle 等。这些形状有自己的属性(如半径、边长)和一些可能的高层逻辑(如 resize)。
- 维度二:绘制的方式 (Implementation Hierarchy): 你可以用 VectorDrawingAPI, RasterDrawingAPI, 未来可能有 OpenGLDrawingAPI, MetalDrawingAPI 等。这些 API 负责底层的实际绘制操作。
桥接模式让这两个维度可以独立地扩展。你可以增加新的形状而不需要修改已有的绘制 API 代码,也可以增加新的绘制 API 而不需要修改形状类的代码(只要新的 API 实现了 DrawingAPI 接口定义的原语)。
- 避免了类爆炸 (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),呈线性增长,扩展性大大提高。
- 提高了灵活性:
- 你可以在运行时决定一个形状对象具体使用哪个 DrawingAPI 来绘制,只需要在创建 Shape 对象时传入不同的 DrawingAPI 实例即可。例如,同一个 Circle 的定义,可以轻易地切换绘制方式。
- 实现细节对客户透明:
- 客户端代码(如 main 函数)只需要与 Shape 抽象层交互 (shape.draw()),不需要关心它内部是用哪个 DrawingAPI 实现的。
总结
- 桥接模式通过将抽象部分与实现部分分离,使用组合/聚合关系替换继承关系,来处理多维度变化的问题。
- 它极大地提高了系统的灵活性和可扩展性,允许两个维度独立地演进。
- 虽然会增加一些类的数量,但在需要解耦抽象和实现,或者避免类爆炸的场景下非常有用。
记住它的核心:抽象持有实现的引用,继承变组合,两套体系独立飞翔。
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/0b9cc2c2-0285-4c15-94ed-25e03a4f8796
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts