1. 程式人生 > 其它 >《設計模式之美》橋接模式:如何實現支援不同型別和渠道的訊息推送系統?

《設計模式之美》橋接模式:如何實現支援不同型別和渠道的訊息推送系統?

技術標籤:課程學習筆記設計模式橋接模式

王爭《設計模式之美》學習筆記

橋接模式的原理解析

  • 橋接模式,也叫作橋樑模式,英文是 Bridge Design Pattern。
  • GoF 的《設計模式》一書中,橋接模式是這麼定義的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻譯成中文就是:“將抽象和實現解耦,讓它們可以獨立變化。”
  • 關於橋接模式,很多書籍、資料中,還有另外一種理解方式:“一個類存在兩個(或多個)獨立變化的維度,我們通過組合的方式,讓這兩個(或多個)維度可以獨立進行擴充套件。”通過組合關係來替代繼承關係,避免繼承層次的指數級爆炸。這種理解方式非常類似於,我們之前講過的“組合優於繼承”設計原則。

文中示例

  • JDBC驅動是橋接模式的經典應用。
Class.forName("com.mysql.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.jdbc.Driver 換成 oracle.jdbc.driver.OracleDriver 就可以了。
  • 也有更靈活的實現方式,我們可以把需要載入的 Driver 類寫到配置檔案中,當程式啟動的時候,自動從配置檔案中載入,這樣在切換資料庫的時候,我們都不需要修改程式碼,只需要修改配置檔案就可以了。
  • 不管是改程式碼還是改配置,在專案中,從一個數據庫切換到另一種資料庫,都只需要改動很少的程式碼,或者完全不需要改動程式碼,那如此優雅的資料庫切換是如何實現的呢?要弄清楚這個問題,我們先從 com.mysql.jdbc.Driver 這個類的程式碼看起。
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    /**
    * Construct a new driver and register it with DriverManager
    * @throws SQLException if a database error occurs.
    */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}
  • 結合 com.mysql.jdbc.Driver 的程式碼實現,我們可以發現,當執行 Class.forName(“com.mysql.jdbc.Driver”) 這條語句的時候,實際上是做了兩件事情:
    • 第一件事情是要求 JVM 查詢並載入指定的 Driver 類。
    • 第二件事情是執行該類的靜態程式碼,也就是將 MySQL Driver 註冊到 DriverManager 類中。
  • 當我們把具體的 Driver 實現類(比如,com.mysql.jdbc.Driver)註冊到 DriverManager 之後,後續所有對 JDBC 介面的呼叫,都會委派到對具體的 Driver 實現類來執行。而 Driver 實現類都實現了相同的介面(java.sql.Driver),這也是可以靈活切換 Driver 的原因。下面看下 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()));
  }
  //...
}
  • 在 JDBC 這個例子中,什麼是“抽象”?什麼是“實現”呢?
    • 實際上,JDBC 本身就相當於“抽象”。注意,這裡所說的“抽象”,指的並非“抽象類”或“介面”,而是跟具體的資料庫無關的、被抽象出來的一套“類庫”。
    • 具體的 Driver(比如,com.mysql.jdbc.Driver)就相當於“實現”。注意,這裡所說的“實現”,也並非指“介面的實現類”,而是跟具體資料庫相關的一套“類庫”。
    • JDBC 和 Driver 獨立開發,通過物件之間的組合關係,組裝在一起。JDBC 的所有邏輯操作,最終都委託給 Driver 來執行。

橋接模式的應用舉例

  • 在第16節中,我們講過一個API介面監控告警的例子,關於傳送告警資訊那部分程式碼,我們先來看最簡單、最直接的一種實現方式。
public enum NotificationEmergencyLevel {
  SEVERE, URGENCY, NORMAL, TRIVIAL
}
public class Notification {
  private List<String> emailAddresses;
  private List<String> telephones;
  private List<String> wechatIds;
  public Notification() {}
  public void setEmailAddress(List<String> emailAddress) {
    this.emailAddresses = emailAddress;
  }
  public void setTelephones(List<String> telephones) {
    this.telephones = telephones;
  }
  public void setWechatIds(List<String> wechatIds) {
    this.wechatIds = wechatIds;
  }
  public void notify(NotificationEmergencyLevel level, String message) {
    if (level.equals(NotificationEmergencyLevel.SEVERE)) {
      //...自動語音電話
    } else if (level.equals(NotificationEmergencyLevel.URGENCY)) {
      //...發微信
    } else if (level.equals(NotificationEmergencyLevel.NORMAL)) {
      //...發郵件
    } else if (level.equals(NotificationEmergencyLevel.TRIVIAL)) {
      //...發郵件
    }
  }
}
//在API監控告警的例子中,我們如下方式來使用Notification類:
public class ErrorAlertHandler extends AlertHandler {
  public ErrorAlertHandler(AlertRule rule, Notification notification){
    super(rule, notification);
  }
  @Override
  public void check(ApiStatInfo apiStatInfo) {
    if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi()).getMaxErrorCount()) {
    	notification.notify(NotificationEmergencyLevel.SEVERE, "...");
    }
  }
}
  • Notification 類的程式碼實現有一個最明顯的問題,那就是有很多 if-else 分支邏輯。
  • 針對 Notification 的程式碼,我們將不同渠道的傳送邏輯剝離出來,形成獨立的訊息傳送類(MsgSender 相關類)。
  • 其中,Notification 類相當於抽象,MsgSender 類相當於實現,兩者可以獨立開發,通過組合關係(也就是橋樑)任意組合在一起。
public interface MsgSender {
  void send(String message);
}
public class TelephoneMsgSender implements MsgSender {
  private List<String> telephones;
  public TelephoneMsgSender(List<String> telephones) {
    this.telephones = telephones;
  }
  @Override
  public void send(String message) {
    //...
  }
}
public class EmailMsgSender implements MsgSender {
  // 與TelephoneMsgSender程式碼結構類似,所以省略...
}
public class WechatMsgSender implements MsgSender
{
  // 與TelephoneMsgSender程式碼結構類似,所以省略...
}
public abstract class Notification {
  protected MsgSender msgSender;
  public Notification(MsgSender msgSender) {
    this.msgSender = msgSender;
  }
  public abstract void notify(String message);
}
public class SevereNotification extends Notification {
  public SevereNotification(MsgSender msgSender) {
    super(msgSender);
  }
  @Override
  public void notify(String message) {
    msgSender.send(message);
  }
}
public class UrgencyNotification extends Notification {
  // 與SevereNotification程式碼結構類似,所以省略...
}
public class NormalNotification extends Notification {
  // 與SevereNotification程式碼結構類似,所以省略...
}
public class TrivialNotification extends Notification {
  // 與SevereNotification程式碼結構類似,所以省略...
}