橋接(Bridge)模式是指將抽象部分與實現部分相分離,使它們都可以獨立的發生變化。
一、橋接模式介紹
我們知道,抽象部分一般與實現部分連線有兩種方式:繼承和實現。那麼如何將其解耦分離,橋接模式提供一種方式,也就是將強關聯轉為弱關聯,將繼承轉換為組合關係。如下圖所示,取消兩者的繼承關係,改用組合關係:
1.1 橋接模式的結構
我們可以看看橋接模式是怎麼解耦,利用組合連線抽象和實現部分,如下所示:
其結構中包含如下角色:
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驅動注入到DriverManager
,registerDriver()
方法具體如下:
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;
//...
}
綜上我們可以畫出對應的類圖:
參考資料
http://c.biancheng.net/view/1364.html