桥接模式,你还不懂?

200 阅读3分钟

什么是桥接模式

桥接模式,也称为桥梁模式,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.Drivercom.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 来执行。

微信图片_20211116235356.jpg

桥接模式解决的问题

解决的问题:

  • 抽象及其实现应该相互独立地定义和扩展。
  • 应避免抽象与其实现之间的编译时绑定,以便可以在运行时选择实现。

在使用子类化时,不同的子类以不同的方式实现一个抽象类。但是实现在编译时绑定到抽象并且不能在运行时更改。

桥接设计模式描述了什么解决方案?

  • 将抽象 ( Abstraction) 与其实现 ( Implementor) 分开,将它们放在单独的类层次结构中。
  • 实现Abstraction在以下方面(通过委托给)一个Implementor对象。