《設計模式之美》橋接模式:如何實現支援不同型別和渠道的訊息推送系統?
阿新 • • 發佈:2021-02-03
王爭《設計模式之美》學習筆記
橋接模式的原理解析
- 橋接模式,也叫作橋樑模式,英文是 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程式碼結構類似,所以省略...
}