定义
桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern
。
在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”
翻译成中文就是:“将抽象和实现进行解耦,让它们可以独立变化。”
说明
关于桥接模式,很多书籍、资料中,还有另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。” 通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则。
桥接模式的代码实现非常简单,但是理解起来稍微有点难度,并且应用场景也比较局限,所以,相当于代理模式来说,桥接模式在实际的项目中并没有那么常用,你只需要简单了解,见到能认识就可以,并不是我们学习的重点。
经典应用
JDBC驱动
JDBC 驱动是桥接模式的经典应用。
我们先来看一下,如何利用 JDBC 驱动来查询数据库。
具体的代码如下所示:
Class.forName("com.mysql.jdbc.Driver");//加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_password";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
String query = "select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {
rs.getString(1);
rs.getInt(2);
}
如果我们想要把 MySQL 数据库换成 Oracle 数据库,只需要把第一行代码中的 com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 就可以了。
当然,也有更灵活的实现方式,我们可以把需要加载的 Driver 类写到配置文件中,当程序启动的时候,自动从配置文件中加载,这样在切换数据库的时候,我们都不需要修改代码,只需要修改配置文件就可以了。
不管是改代码还是改配置,在项目中,从一个数据库切换到另一种数据库,都只需要改动很少的代码,或者完全不需要改动代码,那如此优雅的数据库切换是如何实现的呢?
源码之下无秘密。要弄清楚这个问题,我们先从 com.mysql.jdbc.Driver 这个类的代码看起。
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
* @throws SQLException if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
我们可以发现,当执行 Class.forName(“com.mysql.jdbc.Driver”) 这条语句的时候,实际上是做了两件事情。
第一件事情是要求 JVM 查找并加载指定的 Driver 类
第二件事情是执行该类的静态代码,也就是将 MySQL Driver 注册到 DriverManager 类中。
再来看一下,DriverManager 类是干什么用的。
具体的代码如下所示。
public class DriverManager {
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
//...
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
//...
public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {
throw new NullPointerException();
}
}
public static Connection getConnection(String url, String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
//...
}
当我们把具体的 Driver 实现类(比如,com.mysql.jdbc.Driver)注册到 DriverManager 之后,后续所有对 JDBC 接口的调用,都会委派到对具体的 Driver 实现类来执行。而 Driver 实现类都实现了相同的接口(java.sql.Driver ),这也是可以灵活切换 Driver 的原因。
那在 JDBC 这个例子中,什么是“抽象”?什么是“实现”呢?
实际上,JDBC 本身就相当于“抽象”。注意,这里所说的“抽象”,指的并非“抽象类”或“接口”,而是跟具体的数据库无关的、被抽象出来的一套“类库”。
具体的 Driver(比如,com.mysql.jdbc.Driver)就相当于“实现”。注意,这里所说的“实现”,也并非指“接口的实现类”,而是跟具体数据库相关的一套“类库”。
JDBC 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。
**应用场景**
- 希望拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。
- 希望在几个独立维度上扩展一个类, 可使用桥接模式。
- 希望在运行时切换不同实现方法, 可使用桥接模式。
设计模式系列阅读目录
- 设计模式:创建型—单例模式
- 设计模式:创建型—原型模式
- 设计模式:创建型—工厂模式
- 设计模式:创建型—建造者模式
- 设计模式:结构型—享元模式
- 设计模式:结构型—组合模式
- 设计模式:结构型—代理模式
- 设计模式:结构型—桥接模式
- 设计模式:结构型—装饰器模式
- 设计模式:结构型—适配器模式
- 设计模式:结构型—门面模式
- 设计模式:行为型—中介模式
- 设计模式:行为型—命令模式
- 设计模式:行为型—备忘录模式
- 设计模式:行为型—模板模式
- 设计模式:行为型—状态模式
- 设计模式:行为型—策略模式
- 设计模式:行为型—职责链模式
- 设计模式:行为型—观察者模式
- 设计模式:行为型—解释器模式
- 设计模式:行为型—访问者模式
- 设计模式:行为型—迭代器模式