Java設計模式之(六)——橋接模式

YSOcean 發表於 2021-11-23
Java 設計模式

1、什麼是橋接模式?

Decouple an abstraction from its implementation so that the two can vary independently.

橋接模式(Bridge Pattern):將抽象和實現解耦, 使得兩者可以獨立地變化。

另外一種解釋是:一個類存在兩個(或多個)獨立變化的維度,我們通過組合的方式,讓這兩個(或多個)維度可以獨立進行擴充套件。

聽起來可能還是很深奧,沒關係,下面通過例子講解。

2、橋接模式定義

image-20210908222425355

①、Abstraction

抽象化角色:它的主要職責是定義出該角色的行為, 同時儲存一個對實現化角色的引用, 該角色一般是抽象類。

②、Implementor

實現化角色:它是介面或者抽象類, 定義角色必需的行為和屬性。

③、RefinedAbstraction

修正抽象化角色:它引用實現化角色對抽象化角色進行修正。

④、ConcreteImplementor

具體實現化角色:它實現介面或抽象類定義的方法和屬性。

3、橋接模式通用程式碼實現

實現化類:

public interface Implementor {

    void doSomething();
}

具體實現化類:

public class ConcreteImplementor1 implements Implementor{
    @Override
    public void doSomething() {
        // 具體業務邏輯處理
    }
}
public class ConcreteImplementor2 implements Implementor{
    @Override
    public void doSomething() {
        // 具體業務邏輯
    }
}

這裡定義了兩個,可能有多個。

抽象化角色:

public abstract class Abstraction {
    // 定義對實現化角色的引用
    private Implementor implementor;

    public Abstraction(Implementor implementor){
        this.implementor = implementor;
    }

    // 自身的行為和屬性
    public void request(){
        this.implementor.doSomething();
    }

    // 獲取實現化角色
    public Implementor getImplementor(){
        return implementor;
    }
}

修正抽象化角色:

public class RefinedAbstraction extends  Abstraction{
    // 覆寫建構函式
    public RefinedAbstraction(Implementor implementor){
        super(implementor);
    }

    // 修正父類的行為
    @Override
    public void request() {
        super.request();
    }
}

測試:

public class BridgeClient {
    public static void main(String[] args) {
        // 定義一個實現化角色
        Implementor implementor = new ConcreteImplementor1();
        // 定義一個抽象化角色
        Abstraction abstraction = new RefinedAbstraction(implementor);
        // 執行方法
        abstraction.request();

    }
}

如果我們的實現化角色有很多的子介面, 然後是一堆的子實現。 在建構函式中傳遞一個明確的實現者, 程式碼也是很清晰的。

4、橋接模式經典例子—JDBC

我們在剛開始用 JDBC 直連資料庫的時候,會有這樣一段程式碼:

Class.forName("com.mysql.cj.jdbc.Driver");//載入及註冊JDBC驅動程式
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_password";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
String query = "select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {
  rs.getString(1);
  rs.getInt(2);
}

如果我們想要把 MySQL 資料庫換成 Oracle 資料庫,只需要把第一行程式碼中的 com.mysql.cj.jdbc.Driver 換成oracle.jdbc.driver.OracleDriver 就可以了。

這種優雅的實現資料庫切換方式就是利用了橋接模式。

我們首先看 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!");
        }
    }
}

這段程式碼 Class.forName("com.mysql.cj.jdbc.Driver") 作用有兩個:

①、要求 JVM 查詢並載入指定的 Driver 類。

②、執行該類的靜態程式碼,也就是將 MySQL Driver 註冊到 DriverManager 類中。

接著我們看 DriverManager 類:

public class DriverManager {
  private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

  //...
  static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
  }
  //...

  public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
    if (driver != null) {
      registeredDrivers.addIfAbsent(new DriverInfo(driver));
    } else {
      throw new NullPointerException();
    }
  }

  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()));
  }
  //...
}

當我們把具體的 Driver 實現類(比如,com.mysql.cj.jdbc.Driver)註冊到 DriverManager 之後,後續所有對 JDBC 介面的呼叫,都會委派到對具體的 Driver 實現類來執行。而 Driver 實現類都實現了相同的介面(java.sql.Driver ),這也是可以靈活切換 Driver 的原因。

image-20210908233556259

5、橋接模式優點

①、抽象和實現分離

這也是橋樑模式的主要特點, 它完全是為了解決繼承的缺點而提出的設計模式。 在該模式下, 實現可以不受抽象的約束, 不用再繫結在一個固定的抽象層次上。

②、優秀的擴充能力

看看我們的例子, 想增加實現? 沒問題! 想增加抽象, 也沒有問題! 只要對外暴露的介面層允許這樣的變化, 我們已經把變化的可能性減到最小。

③、實現細節對客戶透明

客戶不用關心細節的實現, 它已經由抽象層通過聚合關係完成了封裝。

6、橋接模式應用場景

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

②、對於那些不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。

③、一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴充套件。