????????上一節(jié)課我們學(xué)習(xí)了第一種結(jié)構(gòu)型模式:代理模式。它在不改變?cè)碱悾ɑ蛘呓斜淮眍悾┐a的情況下,通過(guò)引入代理類來(lái)給原始類附加功能。代理模式在平時(shí)的開(kāi)發(fā)經(jīng)常被用到,常用在業(yè)務(wù)系統(tǒng)中開(kāi)發(fā)一些非功能性需求,比如:監(jiān)控、統(tǒng)計(jì)、鑒權(quán)、限流、事務(wù)、冪等、日志。
????????今天,我們?cè)賹W(xué)習(xí)另外一種結(jié)構(gòu)型模式:橋接模式。橋接模式的代碼實(shí)現(xiàn)非常簡(jiǎn)單,但是理解起來(lái)稍微有點(diǎn)難度,并且應(yīng)用場(chǎng)景也比較局限,所以,相當(dāng)于代理模式來(lái)說(shuō),橋接模式在實(shí)際的項(xiàng)目中并沒(méi)有那么常用,你只需要簡(jiǎn)單了解,見(jiàn)到能認(rèn)識(shí)就可以,并不是我們學(xué)習(xí)的重點(diǎn)。
橋接模式的原理
對(duì)于這個(gè)模式有兩種不同的理解方式。
當(dāng)然,這其中“最純正”的理解方式,當(dāng)屬GoF的《設(shè)計(jì)模式》一書(shū)中對(duì)橋接模式的定義。畢竟,這23種經(jīng)典的設(shè)計(jì)模式,最初就是由這本書(shū)總結(jié)出來(lái)的。在GoF的《設(shè)計(jì)模式》一書(shū)中,橋接模式是這么定義的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻譯成中文就是:“將抽象和實(shí)現(xiàn)解耦,讓它們可以獨(dú)立變化。”
關(guān)于橋接模式,很多書(shū)籍、資料中,還有另外一種理解方式:“一個(gè)類存在兩個(gè)(或多個(gè))獨(dú)立變化的維度,我們通過(guò)組合的方式,讓這兩個(gè)(或多個(gè))維度可以獨(dú)立進(jìn)行擴(kuò)展?!蓖ㄟ^(guò)組合關(guān)系來(lái)替代繼承關(guān)系,避免繼承層次的指數(shù)級(jí)爆炸。這種理解方式非常類似于,我們之前講過(guò)的“組合優(yōu)于繼承”設(shè)計(jì)原則,所以,這里我就不多解釋了。我們重點(diǎn)看下GoF的理解方式。
其關(guān)鍵在于避免繼承層次的指數(shù)級(jí)爆炸。
GoF給出的定義非常的簡(jiǎn)短,單憑這一句話,估計(jì)沒(méi)幾個(gè)人能看懂是什么意思。所以,我們通過(guò)JDBC驅(qū)動(dòng)的例子來(lái)解釋一下。JDBC驅(qū)動(dòng)是橋接模式的經(jīng)典應(yīng)用。我們先來(lái)看一下,如何利用JDBC驅(qū)動(dòng)來(lái)查詢數(shù)據(jù)庫(kù)。具體的代碼如下所示:?
Class.forName("com.mysql.jdbc.Driver");//加載及注冊(cè)JDBC驅(qū)動(dòng)程序
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數(shù)據(jù)庫(kù)換成Oracle數(shù)據(jù)庫(kù),只需要把第一行代碼中的com.mysql.jdbc.Driver換成oracle.jdbc.driver.OracleDriver就可以了。當(dāng)然,也有更靈活的實(shí)現(xiàn)方式,我們可以把需要加載的Driver類寫(xiě)到配置文件中,當(dāng)程序啟動(dòng)的時(shí)候,自動(dòng)從配置文件中加載,這樣在切換數(shù)據(jù)庫(kù)的時(shí)候,我們都不需要修改代碼,只需要修改配置文件就可以了。
不管是改代碼還是改配置,在項(xiàng)目中,從一個(gè)數(shù)據(jù)庫(kù)切換到另一種數(shù)據(jù)庫(kù),都只需要改動(dòng)很少的代碼,或者完全不需要改動(dòng)代碼,那如此優(yōu)雅的數(shù)據(jù)庫(kù)切換是如何實(shí)現(xiàn)的呢?
package com.mysql.jdbc;
import java.sql.SQLException;
/**
* The Java SQL framework allows for multiple database drivers. Each driver should supply a class that implements the Driver interface
*
* When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a
* driver by doing Class.forName("foo.bah.Driver")
*/
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
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()
}
}
結(jié)合com.mysql.jdbc.Driver的代碼實(shí)現(xiàn),我們可以發(fā)現(xiàn),當(dāng)執(zhí)行Class.forName(“com.mysql.jdbc.Driver”)這條語(yǔ)句的時(shí)候,實(shí)際上是做了兩件事情。第一件事情是要求JVM查找并加載指定的Driver類,第二件事情是執(zhí)行該類的靜態(tài)代碼,也就是將MySQL Driver注冊(cè)到DriverManager類中。
這個(gè)也太牛了吧。直接加載類并執(zhí)行其靜態(tài)代碼塊
那么,DriverManager類是干什么用的。當(dāng)我們把具體的Driver實(shí)現(xiàn)類(比如,com.mysql.jdbc.Driver)注冊(cè)到DriverManager之后,后續(xù)所有對(duì)JDBC接口的調(diào)用,都會(huì)委派到對(duì)具體的Driver實(shí)現(xiàn)類來(lái)執(zhí)行。而Driver實(shí)現(xiàn)類都實(shí)現(xiàn)了相同的接口(java.sql.Driver ),這也是可以靈活切換Driver的原因。
?這里僅僅展示DriverManager類的一些重要屬性和方法,其他的進(jìn)行省略方便展示。
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()));
}
//...
}
橋接模式的定義是將“抽象和實(shí)現(xiàn)解耦”,讓它們可以獨(dú)立變化。那弄懂定義中“抽象”和“實(shí)現(xiàn)”兩個(gè)概念,就是理解橋接模式的關(guān)鍵。那在JDBC這個(gè)例子中,什么是“抽象”?什么是“實(shí)現(xiàn)”呢?
實(shí)際上,JDBC本身就相當(dāng)于“抽象”。注意,這里所說(shuō)的“抽象”,指的并非“抽象類”或“接口”,而是跟具體的數(shù)據(jù)庫(kù)無(wú)關(guān)的、被抽象出來(lái)的一套“類庫(kù)”。具體的Driver(比如,com.mysql.jdbc.Driver)就相當(dāng)于“實(shí)現(xiàn)”。注意,這里所說(shuō)的“實(shí)現(xiàn)”,也并非指“接口的實(shí)現(xiàn)類”,而是跟具體數(shù)據(jù)庫(kù)相關(guān)的一套“類庫(kù)”。JDBC和Driver獨(dú)立開(kāi)發(fā),通過(guò)對(duì)象之間的組合關(guān)系,組裝在一起。JDBC的所有邏輯操作,最終都委托給Driver來(lái)執(zhí)行。
有時(shí)候,我們可以簡(jiǎn)單的理解為抽象的是接口,實(shí)現(xiàn)是接口的實(shí)現(xiàn)。
?
具體可以學(xué)習(xí):JDBC和橋接模式 - 咕~咕咕 - 博客園 (cnblogs.com)
橋接模式的應(yīng)用舉例。
API接口監(jiān)控告警的例子:根據(jù)不同的告警規(guī)則,觸發(fā)不同類型的告警。告警支持多種通知渠道,包括:郵件、短信、微信、自動(dòng)語(yǔ)音電話。通知的緊急程度有多種類型,包括:SEVERE(嚴(yán)重)、URGENCY(緊急)、NORMAL(普通)、TRIVIAL(無(wú)關(guān)緊要)。不同的緊急程度對(duì)應(yīng)不同的通知渠道。比如,SERVE(嚴(yán)重)級(jí)別的消息會(huì)通過(guò)“自動(dòng)語(yǔ)音電話”告知相關(guān)人員。
一個(gè)變化的維度是通知渠道:郵件、短信、微信和自動(dòng)語(yǔ)言電話等等
一個(gè)變化的維度是通知緊急程度:嚴(yán)重、緊急、普通和無(wú)關(guān)緊要。
在我們來(lái)一塊實(shí)現(xiàn)一下。我們先來(lái)看最簡(jiǎn)單、最直接的一種實(shí)現(xiàn)方式。代碼如下所示:
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)) {
//...自動(dòng)語(yǔ)音電話
} else if (level.equals(NotificationEmergencyLevel.URGENCY)) {
//...發(fā)微信
} else if (level.equals(NotificationEmergencyLevel.NORMAL)) {
//...發(fā)郵件
} else if (level.equals(NotificationEmergencyLevel.TRIVIAL)) {
//...發(fā)郵件
}
}
}
//在API監(jiān)控告警的例子中,我們?nèi)缦路绞絹?lái)使用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, "...");
}
}
}
針對(duì)Notification的代碼,我們將不同渠道的發(fā)送邏輯剝離出來(lái),形成獨(dú)立的消息發(fā)送類(MsgSender相關(guān)類)。其中,Notification類相當(dāng)于抽象,MsgSender類相當(dāng)于實(shí)現(xiàn),兩者可以獨(dú)立開(kāi)發(fā),通過(guò)組合關(guān)系(也就是橋梁)任意組合在一起。所謂任意組合的意思就是,不同緊急程度的消息和發(fā)送渠道之間的對(duì)應(yīng)關(guān)系,不是在代碼中固定寫(xiě)死的,我們可以動(dòng)態(tài)地去指定(比如,通過(guò)讀取配置來(lái)獲取對(duì)應(yīng)關(guān)系)。
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代碼結(jié)構(gòu)類似,所以省略...
}
public class WechatMsgSender implements MsgSender {
// 與TelephoneMsgSender代碼結(jié)構(gòu)類似,所以省略...
}
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代碼結(jié)構(gòu)類似,所以省略...
}
public class NormalNotification extends Notification {
// 與SevereNotification代碼結(jié)構(gòu)類似,所以省略...
}
public class TrivialNotification extends Notification {
// 與SevereNotification代碼結(jié)構(gòu)類似,所以省略...
}
這樣重構(gòu)以后,就可以進(jìn)行兩個(gè)維度的擴(kuò)展,繼續(xù)增加通知渠道和通知的緊急類型。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-551374.html
總結(jié):橋接模式的應(yīng)用場(chǎng)景主要是針對(duì)于;一個(gè)類存在兩個(gè)(或多個(gè))獨(dú)立變化的維度,我們通過(guò)組合的方式,讓這兩個(gè)(或多個(gè))維度可以獨(dú)立進(jìn)行擴(kuò)展?!蓖ㄟ^(guò)組合關(guān)系來(lái)替代繼承關(guān)系,避免繼承層次的指數(shù)級(jí)爆炸。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-551374.html
到了這里,關(guān)于橋接模式:如何實(shí)現(xiàn)支持不同類型和渠道的消息推送系統(tǒng)?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!