設計模式學習筆記(九)橋接模式及其應用

歸斯君發表於2022-03-30

橋接(Bridge)模式是指將抽象部分與實現部分相分離,使它們都可以獨立的發生變化。

一、橋接模式介紹

我們知道,抽象部分一般與實現部分連線有兩種方式:繼承和實現。那麼如何將其解耦分離,橋接模式提供一種方式,也就是將強關聯轉為弱關聯,將繼承轉換為組合關係。如下圖所示,取消兩者的繼承關係,改用組合關係:

image-20220330181522305

1.1 橋接模式的結構

我們可以看看橋接模式是怎麼解耦,利用組合連線抽象和實現部分,如下所示:

image-20220330204226055

其結構中包含如下角色:

  • Abstraction:抽象化角色,定義抽象類,包含一個對實現化物件的引用(組合)
  • RefinedAbstraction:擴充套件抽象化角色,實現抽象化角色的子類,由此通過組合關係呼叫實現化角色中的業務方法
  • Implementor:實現化角色的介面,供擴充套件抽象化角色呼叫
  • ImplementorA、ImplementorB:實現化角色的具體實現

1.2 橋接模式的實現

我們可以根據上面的UML圖實現對應的程式碼:

//客戶端類
public class Client {
    public static void main(String[] args) {
        Implementor imple = new ImplementorA();
        Abstraction abs = new RefinedAbstraction(imple);
        abs.Operation();
    }
}
//實現化角色
interface Implementor {
    public void OperationImpl();
}
//具體的實現化角色
class ImplementorA implements Implementor {
    public void OperationImpl() {
        System.out.println("我是具體實現化角色A");
    }
}
class ImplementorB implements Implementor {
    public void OperationImpl() {
        System.out.println("我是具體實現化角色B");
    }
}
//抽象化角色
abstract class Abstraction {
    protected Implementor imple;
    
    protected Abstraction(Implementor imple) {
        this.imple = imple;
    }
    
    public abstract void Operation();
}
//擴充套件抽象化角色
class RefinedAbstraction extends Abstraction {
    protected RefinedAbstraction(Implementor imple) {
        super(imple);
    }
    
    public void Operation() {
        System.out.println("擴充套件抽象化角色被訪問");
        imple.OperationImpl();
    }
}

實現結果:

擴充套件抽象化角色被訪問
我是具體實現化角色A

二、橋接模式的應用場景

2.1 JDBC 驅動器

JDBC為所有的關係型資料庫提供一個通用的標準,這就是一個橋接模式的典型應用。我們先回顧一下JDBC的使用,用JDBC連線MySQL資料庫主要分為這樣幾步:

//1.載入MySQL驅動注入到DriverManager
Class.forName("com.mysql.cj.jdbc.Driver");
//2.提供JDBC連線的URL、使用者名稱和密碼
String url = "jdbc:mysql://localhost:3306/test_db?";
String username = "root";
String password = "root";
//3.建立資料庫的連線
Connection connection = DriverManager.getConnection(url, username, password);
//4.建立statement例項
Statement statement = connection.createStatement();
//5.執行SQL語句
String query = "select * from test";  //查詢語句,也可以換成CRUD的其他語句
ResultSet resultSet = statement.executeQuery(query);
//6.關閉連線物件
connection.close();

我們一步步來看,先看步驟1:

Class.forName("com.mysql.cj.jdbc.Driver");

檢視對應的 com.mysql.cj.jdbc.Driver路徑下的原始碼:

package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

是通過靜態方法呼叫registerDriver()方法來將MySQL驅動注入到DriverManagerregisterDriver()方法具體如下:

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 {
    /* registeredDrivers是一個list,用DriverInfo例項封裝Driver */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }
    println("registerDriver: " + driver);

}

registeredDrivers靜態變數其實是一個list:

public class DriverManager {
    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    //...
}

DriverInfo類中封裝了java.sql.Driver介面:

class DriverInfo {

    final Driver driver;
    DriverAction da;
    DriverInfo(Driver driver, DriverAction action) {
        this.driver = driver;
        da = action;
    }
    //...
}

再看步驟2、3,重點是步驟3

Connection connection = DriverManager.getConnection(url, username, password);

Connection介面是和特定資料庫的連線會話,不同的資料庫的連線會話都不相同:

public interface Connection  extends Wrapper, AutoCloseable {

    Statement createStatement() throws SQLException;
    //...
}

是通過DriverManager中的getConnection方法,從registeredDrivers進行選擇對應資料庫驅動下的連線例項:

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()));
}
// 實際上呼叫的是下面的靜態方法getConnection
//  Worker method called by the public getConnection() methods.
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }

    println("DriverManager.getConnection(\"" + url + "\")");

    // Walk through the loaded registeredDrivers attempting to make a connection.
    // Remember the first exception that gets raised so we can reraise it.
    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());
        }
    }

    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }

    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}

Connection介面的具體實現部分,MySQL的連線是通過兩層實現完成抽象部分的實現:

public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {
    private static final long serialVersionUID = 4009476458425101761L;
    private static final SQLPermission SET_NETWORK_TIMEOUT_PERM = new SQLPermission("setNetworkTimeout");
    //...
}
public interface JdbcConnection extends Connection, MysqlConnection, TransactionEventHandler {
    JdbcPropertySet getPropertySet();

    void changeUser(String var1, String var2) throws SQLException;
    //...
}

綜上我們可以畫出對應的類圖:

image-20220330234125857

參考資料

http://c.biancheng.net/view/1364.html

https://jishuin.proginn.com/p/763bfbd68968

https://www.cnblogs.com/kuluo/p/13038076.html

相關文章