手撕設計模式之消息推送系統——橋接模式
1.業務需求
? 大家好,我是菠菜啊,好久不見,今天給大家帶來的是——橋接模式。老規矩,在介紹這期內容前,我們先來看看這樣的需求:我們現在要做一個消息推送系統,實現純文本消息和html格式消息的推送,推送方式支持email、短信,我們該怎么實現?
2.代碼實現
Talk is cheap,show me your code.
初版實現思路:
? 假設我們有一個父類Message類(消息類型),從它擴展出倆個子類:TextMessage、HtmlMessage,又要支持email、短信推送方式,那么我們需要共創建4個子類才能覆蓋所有組合。
初版代碼如下:
//消息基類
public abstract class Message {abstract void sendMessage(String message);
}
//文本消息類
public class TextMessage extends Message{@Overridevoid sendMessage(String message) {System.out.println("TextMessage:"+message);}
}
//html消息類
public class HtmlMessage extends Message{@Overridevoid sendMessage(String message) {System.out.println("HtmlMessage:"+message);}
}
public class HtmlEmailMessage extends HtmlMessage{@Overridevoid sendMessage(String message) {super.sendMessage(message);System.out.println("send HtmlMessage by eamil");}
}
public class HtmlSmsMessage extends HtmlMessage{@Overridevoid sendMessage(String message) {super.sendMessage(message);System.out.println("send HtmlMessage by sms");}
}
public class TextEmailMessage extends TextMessage{@Overridevoid sendMessage(String message) {super.sendMessage(message);System.out.println("send TextMessage by eamil");}
}
public class TextSmsMessage extends TextMessage{@Overridevoid sendMessage(String message) {super.sendMessage(message);System.out.println("send TextMessage by sms");}
}
public class Client {public static void main(String[] args) {Message message = new TextSmsMessage();message.sendMessage("hello world");Message message2 = new HtmlEmailMessage();message2.sendMessage("<html> <h>hello world </h></html>");}
}
輸出結果:
思考:
? 上述每添加一種消息類型或者消息推送方式,都要新增一個維度的子類,導致類爆炸。而且我們發現消息類型和消息推送方式是倆種獨立維度,我們不應該用繼承的這種方式去拓展,那么該怎么解決呢?
3.代碼優化
//發送方式
public interface MessageSender {void send(String message);
}
//短信發送方式
public class SmsMessageSender implements MessageSender {@Overridepublic void send(String message) {System.out.println("使用sms發送消息:" + message);}
}
//eamil發送方式
public class EmailMessageSender implements MessageSender {@Overridepublic void send(String message) {System.out.println("使用email發送消息:" + message);}
}
//消息類
public abstract class Message2 {protected MessageSender messageSender;public Message2(MessageSender messageSender) {this.messageSender = messageSender;}abstract void sendMessage(String message);
}
//文本消息類
public class TextMessage2 extends Message2{public TextMessage2(MessageSender messageSender) {super(messageSender);}@Overridevoid sendMessage(String message) {System.out.println("TextMessage:"+message);messageSender.send(message);}
}
//html消息類
public class HtmlMessage2 extends Message2{public HtmlMessage2(MessageSender messageSender) {super(messageSender);}@Overridevoid sendMessage(String message) {System.out.println("HtmlMessage:"+message);messageSender.send(message);}
}
輸出結果:
實現代碼結構圖:
思考:
? 上述將發送消息方式抽象出來,消息類中消息的發送方式委托給MessageSender,通過組合的方式實現消息類型(文本、HTML、模板、緊急)和發送渠道(郵件、短信、推送、微信)這兩個維度的獨立變化,職責清晰,擴展性強。這個設計模式完全體現了設計原則中的合成復用原則(Composite Reuse Principle):“優先使用對象組合(Composition),而不是類繼承(Inheritance)來實現代碼復用。”
4.定義與組成
定義:
? 橋接模式(Bridge),將抽象部分與它的實現部分分離,使它們可以獨立地變化。
核心思想:
通過組合替代繼承,將抽象(功能層次)與實現(平臺層次)解耦,解決多維變化導致的類爆炸問題。
組成部分:
-
抽象部分(Abstraction):定義高層控制邏輯的接口,并且持有實現部分的引用
-
精確抽象(Refined Abstraction):擴展抽象功能的子類
-
實現者接口(Implementor):定義底層實現的契約
-
具體實現者(Concrete Implementor):具體的底層實現
5.典型應用
1)JDBC驅動程序(源碼簡化)
//實現者接口 - Driver
public interface Driver {Connection connect(String url, Properties info) throws SQLException;boolean acceptsURL(String url);
}
//具體實現者 - MySQLDriver
public class MySQLDriver implements Driver {@Overridepublic Connection connect(String url, Properties info) {if (!acceptsURL(url)) return null;// 實際建立物理連接Socket socket = new Socket(extractHost(url), extractPort(url));return new MySQLConnectionImpl(socket);}private String extractHost(String url) { /* 解析主機名 */ }private int extractPort(String url) { /* 解析端口號 */ }
}
//抽象接口 - Connection
public interface Connection extends AutoCloseable {Statement createStatement() throws SQLException;PreparedStatement prepareStatement(String sql) throws SQLException;// ... 其他抽象方法
}
//精確抽象 - MySQLConnectionImpl
class MySQLConnectionImpl implements Connection {private final Socket socket;private final OutputStream out;private final InputStream in;public MySQLConnectionImpl(Socket socket) {this.socket = socket;this.out = socket.getOutputStream();this.in = socket.getInputStream();}@Overridepublic Statement createStatement() {return new MySQLStatementImpl(this);}// 實際發送SQL命令到MySQL服務器void sendCommand(String command) {out.write(command.getBytes());out.flush();}// ... 其他具體實現
}
//橋接點 - DriverManager
public class DriverManager {private static final List<Driver> drivers = new CopyOnWriteArrayList<>();public static void registerDriver(Driver driver) {drivers.add(driver);}public static Connection getConnection(String url, String user, String pass) {for (Driver driver : drivers) {if (driver.acceptsURL(url)) {Properties props = new Properties();props.setProperty("user", user);props.setProperty("password", pass);return driver.connect(url, props);}}throw new SQLException("No suitable driver found");}
}
2)GUI開發(AWT/Swing)
windows類中有抽象的窗口接口,并有一個畫窗口的方法,實現不同平臺畫窗口功能委托給具體的平臺實現
// 抽象:Window
public class Window {private WindowImpl impl; // 橋接關鍵public void draw() {impl.deviceDraw(); // 委托給具體平臺實現}
}// 實現:不同OS的圖形實現
interface WindowImpl {void deviceDraw();
}class WindowsWindowImpl implements WindowImpl {...}
class MacWindowImpl implements WindowImpl {...}
6.適用場景
場景特征 | 示例 |
---|---|
存在多個獨立變化維度 | 圖形形狀 × 渲染方式 |
需要運行時切換實現 | 動態切換支付渠道 |
避免永久綁定抽象與實現 | JDBC連接不同數據庫 |
類層次結構爆炸 | 避免創建 N×M 子類組合 |
經驗法則:當聽到"需要根據不同平臺/方式做不同實現"時,優先考慮橋接模式
技術需要沉淀,同樣生活也是~
個人鏈接:博客,歡迎一起交流