什么是桥接模式
桥接模式,也称为桥梁模式,Bridge Design Pattern。
关于桥接模式,网上有俩种不同的理解方式:
- 在 GoF 设计模式中,是这么定义的:Decouple an abstraction from its implementation so that the two can vary independently。汉译:
将抽象和实现解耦,让抽象和实现独立变化。
- 在其他书籍和资料中,衍生了另外的理解方式:
类存在多个独立变化的维度,通过组合方式,让多个维度可以独立扩展。
这种方式类似于组合优于继承原则,避免了继承指数级爆炸。
如何使用桥接模式
你可能觉得,上面的描述有点晦涩难懂,桥接模式确实有点难理解,我们接下来通过例子来结合理解一下桥接模式。
JDBC 驱动是桥接模式的经典应用,我们通过 JDBC 驱动来查询数据库数据:
Class.forName("com.mysql.jdbc.Driver");//加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/demo?user=root&password=123456";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
String sql = "select * from test";
ResultSet rs = stmt.executeQuery(sql);
...
此时,假如我们需要更换数据库,从 mysql 切换到 oracle ,我们需要怎么做呢?
- 更换驱动程序,从
com.mysql.jdbc.Driver
改成oracle.jdbc.driver.OracleDriver
- 修改url,从
jdbc:mysql://localhost:3306...
改成jdbc:oracle:thin:@localhost:3306...
修改以上俩点,即可从 mysql 切换成 oracle ,有人可能会问了,Class#forName
只是加载类,它是如何做到这么优雅地切换数据库呢?
我们先看到,com.mysql.jdbc.Driver
的子类:
package com.mysql.cj.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
Class#forName
执行时,com.mysql.cj.jdbc.Driver
类会被加载,静态代码块会被触发,注册了驱动到 java.sql.DriverManager
, 注意这里注册的驱动是 com.mysql.cj.jdbc.Driver
,com.mysql.cj.jdbc.Driver
实现了 java.sql.Driver
, 这也是能够灵活切换 Driver的原因。
我们再看一下,java.sql.DriverManager
的注册逻辑:
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
注册时,驱动 com.mysql.cj.jdbc.Driver
会被包装成 DriverInfo
,添加到 registeredDrivers
。
驱动添加到 registeredDrivers
后,java.sql.DriverManager
是怎么使用驱动的呢:
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
// 省略部分代码...
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// 省略部分代码...
}
桥接模式的定义是,将抽象和实现解耦,让抽象和实现独立变化。只要我们弄懂什么是抽象,什么是实现,就可以理解好桥接模式。
在 JDBC 的例子中,JDBC 本身就是"抽象"(并非抽象类或者接口,而是抽象出来,与数据库无关的"类库"),而具体的 Driver 则相当于"实现"(并非接口的实现类,而是具体实现数据库的"类库")。JDBC 与 Driver 是独立开发的,通过组合的方式,JDBC 的逻辑操作委托给 Driver 来执行。
桥接模式解决的问题
解决的问题:
- 抽象及其实现应该相互独立地定义和扩展。
- 应避免抽象与其实现之间的编译时绑定,以便可以在运行时选择实现。
在使用子类化时,不同的子类以不同的方式实现一个抽象类。但是实现在编译时绑定到抽象并且不能在运行时更改。
桥接设计模式描述了什么解决方案?
- 将抽象 (
Abstraction
) 与其实现 (Implementor
) 分开,将它们放在单独的类层次结构中。 - 实现
Abstraction
在以下方面(通过委托给)一个Implementor
对象。