首頁 > 軟體

詳解Java設計模式之橋接模式

2022-06-23 14:00:44

一、什麼是橋接模式:

橋接,顧名思義,就是用來連線兩個部分,使得兩個部分可以互相通訊,橋接模式的作用就是為被分離的抽象部分和實現部分搭橋。在現實生活中一個物品在搭配不同的配件時會產生不同的動作和結果,例如一輛賽車搭配的是硬胎或者是軟胎就能夠在乾燥的馬路上行駛,而如果要在下雨的路面行駛,就需要搭配雨胎了,這種根據行駛的路面不同,需要搭配不同的輪胎的變化的情況,我們從軟體設計的角度來分析,就是一個系統由於自身的邏輯,會有兩個或多個維度的變化,而為了應對這種變化,我們就可以使用橋接模式來進行系統的解耦。 橋接模式將一個系統的抽象部分和實現部分分離,使它們都可以獨立地進行變化,對應到上面就是賽車的種類可以相對變化,輪胎的種類可以相對變化,形成一種交叉的關係,最後的結果就是一種賽車對應一種輪胎就能夠成功產生一種結果和行為。

橋接模式將系統的抽象部分與實現部分分離解耦,使他們可以獨立的變化。為了達到讓抽象部分和實現部分獨立變化的目的,橋接模式使用組合關係來代替繼承關係,抽象部分擁有實現部分的介面物件,從而能夠通過這個介面物件來呼叫具體實現部分的功能。也就是說,橋接模式中的橋接是一個單方向的關係,只能夠抽象部分去使用實現部分的物件,而不能反過來。

橋接模式符合“開閉原則”,提高了系統的可拓展性,在兩個變化維度中任意擴充套件一個維度,都不需要修改原來的系統;並且實現細節對客戶不透明,可以隱藏實現細節。但是由於聚合關係建立在抽象層,要求開發者針對抽象進行程式設計,這增加系統的理解和設計難度。

所以,橋接模式一般適用於以下幾種應用場景:

(1) 系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯絡,則可以通過橋接模式使他們在抽象層建立一個關聯關係;

(2) 系統不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加時

(3) 一個類存在兩個獨立變化的維度,而這兩個維度都需要進行擴充套件。

二、UML結構圖

抽象化角色 Abstraction:定義抽象的介面,包含一個對實現化角色的參照,抽象角色的方法需要呼叫實現化角色;擴充套件抽象化角色 RefinedAbstraction:抽象化角色的子類,一般對抽象部分的方法進行完善和擴充套件,實現父類別中的業務方法,並通過組合/聚合關係呼叫實現化角色中的業務方法實現化角色 Implementor:定義具體行為、具體特徵的應用介面,供擴充套件抽象化角色使用,一般情況下是由實現化角色提供基本的操作,而抽象化角色定義基於實現部分基本操作的業務方法;具體實現化角色 ConcreteImplementor:完善實現化角色中定義的具體邏輯。

三、程式碼實現

Implementor 介面類:

public interface Implementor {
    void operationImpl();
}

ConcreteImplementor 介面實現類:

public class ConcreteImplementorA implements Implementor{
    @Override
    public void operationImpl() {
        //具體實現
    }
}
public class ConcreteImplementorB implements Implementor{
    @Override
    public void operationImpl() {
        //具體實現
    }
}

Abstraction 抽象類:

public abstract class Abstraction {
    private Implementor implementor;
    public Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }
    public void operation() {
        implementor.operationImpl();
    }
}

RefinedAbstraction 抽象類的具體實現:

public class RefinedAbstraction extends Abstraction{
    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }
    public void refinedOperation() {
        //對 Abstraction 中的 operation 方法進行擴充套件
    }
}

看了這段通用程式碼之後,橋接模式的結構應該就很清楚了,需要注意的是 RefinedAbstraction 根據實際情況是可以有多個的。 當然上面的 UML 類圖和通用程式碼只是最常用的實現方式而已,在實際使用中可能會有其他的情況,比如 Implementor 只有一個類的情況,雖然這時候可以不去建立 Implementor 介面,精簡類的層次,但是我建議還是需要抽象出實現部分的介面。

四、JDBC原始碼解析-橋接模式

Java 中,我們使用 JDBC 連線資料庫時,在各個資料庫之間進行切換,基本不需要動太多的程式碼,原因就是使用了橋接模式,JDBC 提供統一介面,每種型別的資料庫提供各自的實現,然後由橋接類建立一個連線資料庫的驅動,使用某一個資料庫的時候只需要切換一下就行。接下來我們就對 JDBC 的原始碼做下剖析:

通過原生JDBC API連線MySQL資料庫,則有如下範例程式碼:

Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://<host>:<port>/<database>");

短短兩行程式碼難以看出橋接模式的結構,下面先對原始碼進行一定的分析,理解各個類和介面之間的關係:

1、原始碼分析

(1)Class.forName() 方法:

該方法將返回與給定字串名的類或介面相關聯的 java.lang.Class 類物件,用於在程式執行時動態載入該類或該介面到當前執行緒中,如果 Class.forName() 載入的是一個類,也會執行類中包含的static { } 靜態程式碼段

(2)com.mysql.cj.jdbc.Driver 類

MySQL 將具體的 java.sql.Driver 介面的實現放到了 NonRegisteringDriver 中,com.mysql.cj.jdbc.Driver 類僅包含一段靜態程式碼

其中最關鍵的是靜態程式碼段中的 DriverManager.registerDriver(new Driver()) ,它會在使用者端呼叫Class.forName() 方法載入 com.mysql.cj.jdbc.Driver 類的同時被執行,Driver 類自身的一個範例被註冊到 DriverManager(即儲存到 DriverManager 的靜態欄位 registeredDrivers 內),註冊過程的原始碼如下:

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);
}

registeredDrivers 靜態欄位的型別是實現了 List 介面的 CopyOnWriteArrayList 類,它能夠儲存進一步封裝 java.sql.Driver 介面的 DriverInfo 類範例,DriverInfo 類的宣告程式碼如下:

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

DriverInfo 還包裝了 DriverAction,DriverAction 會在Driver被取消註冊時被呼叫,在 MySQL 的 Driver 在向 DriverManager 進行註冊時,DriverAction 被設定為 null

(3)DriverManager 類:

由上面的分析可得,Class.forName() 方法呼叫後,com.mysql.cj.jdbc.Driver 類被載入,並執行static { } 靜態程式碼段,將 com.mysql.cj.jdbc.Driver 類範例註冊到 DriverManager 中。然後,使用者端會呼叫 DriverManager.getConnection() 方法獲取一個 Connection 資料庫連線範例,該方法的部分原始碼如下:

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
  // ……
  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());
    }
  }
  // ……
}

DriverManager.getConnection() 方法會遍歷 registeredDrivers 靜態欄位,獲取欄位內儲存的每一個 Driver 來嘗試響應使用者端的資料庫連線請求,若所有 Driver 都連線資料庫失敗,則提示連線失敗資訊

(4)Connection介面

Connection 代表和特定資料庫的連線對談,能夠執行SQL語句並在連線的上下文中返回執行結果。因此,DriverManager.getConnection() 方法返回的 Connection 資料庫連線範例根據不同的資料庫有不同的實現,MySQL 的 Connection 介面實現關係

2、原始碼類

對 Driver 和 Connection 進行抽象

橋接模式通過聚合關係代替繼承關係,實現抽象化和實現化部分的解耦。以上述 JDBC 在 MySQL 中的簡略類圖為例,抽象化部分有 DriverManager,實現化部分有 Driver 介面和 Connection 介面。對於不同的資料庫,Driver介面和Connection介面都有自己獨特的實現類。

但是,和 Driver 介面不同的是,Connection 介面與 DriverManager 類的關係只是聯絡較弱的依賴關係,並不符合橋接模式的定義和特點。

橋接模式中的實現化角色 (Implementor) 對應上圖的 Driver 介面,具體實現化 (Concrete Implementor) 角色對應 MysqlDriver、OracleDriver 和 MariadbDriver,擴充套件抽象化 (Refined Abstraction) 角色對應 DriverManager,不具有抽象化 (Abstraction) 角色作為擴充套件抽象化角色的父類別

3、對 JDBC 的觀點

(1)觀點:JDBC採用的是策略模式而不是橋接模式

jdbc是橋接模式還是策略模式?

因為這確實和策略模式十分相似,如果把橋接模式的抽象部分簡化來看,不去設計Abstraction,也就是用 Refined Abstraction 代替 Abstraction,那麼就類似於策略模式的 Context 來使用介面的物件。

但是,橋接模式和策略模式的目的是不一樣的,策略模式屬於物件行為模式(描述物件之間怎樣相互共同作業共同完成單個物件都無法單獨完成的任務,以及怎樣分配職責),它的目的是封裝一系列的演演算法,使得演演算法可以相互替代,並在程式執行的不同時刻選擇合適的演演算法。而橋接模式屬於物件結構模式(描述如何將物件按某種佈局組成更大的結構),它的目的是將抽象與實現分離,使它們可以獨立變化

因此,從設計的目的來看,JDBC採用的並不是策略模式,在一段程式中資料庫驅動並不存在頻繁地相互替換

以上就是詳解Java設計模式之橋接模式的詳細內容,更多關於Java橋接模式的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com