桥接模式
你正在开发一个数据库访问框架,需要支持 MySQL、PostgreSQL、Oracle 三种数据库,同时还需要支持增删改查、事务、分页三种操作方式。
按照传统思路,你可能会这样设计类结构:
如果有 3 种数据库和 3 种操作,就需要 9 个类。如果再加 2 种数据库、加 2 种操作,类数量会爆炸式增长。这不是继承该解决的问题。
问题的根源在于:数据库类型和操作类型是两个独立变化的维度,不应该绑在一起。MySQL 的查询和 PostgreSQL 的查询,核心逻辑是相似的;查询 MySQL 和查询 PostgreSQL,核心逻辑也是相似的。
桥接模式正是解决这类问题的利器。
桥接模式的核心思想
桥接模式(Bridge Pattern)将抽象部分与实现部分分离,使它们可以独立变化。它通过组合而非继承来连接抽象和实现,让两者可以独立扩展而互不影响。
抽象部分定义高层业务逻辑,实现部分提供底层平台能力。两者通过「桥」——即组合关系——连接起来,而不是通过继承绑死在一起。
JDBC:桥接模式的经典应用
JDBC(Java Database Connectivity)是桥接模式最经典的应用案例。Java 应用程序只需要面对 JDBC 接口编程,而具体的数据库驱动由数据库厂商实现。
JDBC 的接口设计:
JDBC 的设计精髓在于:Java 应用只需要学会一套 API,无论底层是 MySQL、PostgreSQL 还是 Oracle,代码逻辑几乎不变。切换数据库只需要换驱动包和 URL。
桥接模式的实现
让我们实现一个跨平台的图形渲染系统:
使用示例:
输出:
类的数量:如果有 M 种图形和 N 种引擎,使用继承需要 M × N 个类,而使用桥接模式只需要 M + N 个类。
桥接模式 vs 装饰器模式 vs 适配器模式
三者都涉及到「包装」,但意图和应用场景不同:
桥接模式解决的是「类爆炸」问题——当一个事物有两个独立变化的维度时,不应该用继承把两者绑在一起。
桥接模式的应用场景
JDBC 驱动
JDBC 是桥接模式最经典的应用。DriverManager 是抽象层,Driver 是实现接口,各厂商驱动是具体实现。
跨平台 UI 组件
日志框架
SLF4J 是日志门面(外观),具体的日志实现(Logback、Log4j2)通过桥接模式插拔:
桥接模式的适用场景
适用场景
- 一个类有两个独立变化的维度,且都需要扩展
- 不希望在两个维度之间使用继承(避免类爆炸)
- 希望在运行时切换实现
- 子系统需要分层,每个层次通过桥接连接
不适用场景
- 两个维度不会独立变化
- 抽象和实现已经绑死,改动成本太高
- 简单的两维度扩展,继承就够用
桥接模式增加了代码复杂度(多了一层抽象),并且需要维护两个独立的部分。只有当确实存在两个独立变化的维度时,才值得使用。如果只有一种变化维度,继承或组合都更简单。
思考题
问题 1:JDBC 的 DriverManager.getConnection() 为什么能自动找到合适的驱动?
参考答案
这是通过 Java 的服务加载机制(Service Provider)实现的:
- 各数据库驱动 JAR 包中的
META-INF/services/java.sql.Driver文件声明了驱动类 DriverManager在初始化时会调用ServiceLoader.load(Driver.class)扫描所有已注册的驱动- 当调用
getConnection(url)时,DriverManager遍历所有驱动,询问acceptsURL(url)哪个返回true - 找到匹配的驱动后,调用其
connect(url, info)建立连接
这种设计让 JDBC 驱动可以热插拔:添加一个驱动 JAR 包,不需要修改任何代码,应用程序就能使用新数据库。
问题 2:桥接模式和策略模式都涉及到「组合」,有什么区别?
参考答案
两者的关键区别在于意图和使用时机:
简单判断:如果两个维度「天然独立」,如数据库类型和 SQL 操作,就用桥接模式;如果需要在运行时「选择算法」,就用策略模式。
问题 3:在 JDBC 中,如果需要同时使用 MySQL 和 PostgreSQL,应该怎么设计?
参考答案
有三种方案:
方案 1:同一时刻单一连接 应用根据业务逻辑选择不同的数据源,代码层面不混合使用:
方案 2:抽象数据源层 在业务层和数据访问层之间引入数据源抽象:
方案 3:动态路由 根据配置或请求参数动态选择数据源:
方案 2 更符合桥接模式的思想,且易于测试和扩展。