觀察者模式
觀察者模式屬于行為模式,個人理解:和發布訂閱者魔模式是有區別的
細分有兩種:推模式和拉模式兩種,具體區別在于推模式會自帶推送參數,拉模式是在接收通知后要自己獲取更新參數
觀察者模式(Observer Pattern)的使用場景主要圍繞對象間一對多的依賴關系,當一個對象(被觀察者)的狀態變化需要自動通知其他多個對象(觀察者)時,該模式能有效解耦代碼。以下是典型的使用場景和案例:
應用場景
1. GUI 事件處理
場景:用戶界面組件(如按鈕、輸入框)的狀態變化需要觸發多個事件監聽器。
案例:
- 點擊按鈕后,觸發日志記錄、界面更新、數據提交等多個操作。
- 輸入框內容變化時,實時校驗輸入合法性并更新提示信息。
框架應用:Java Swing、Android 的OnClickListener
、JavaScript 的addEventListener
。
2. 實時數據同步
場景:數據源的變更需要實時同步到多個客戶端或組件。
案例:
-
股票行情系統:股價變動時,所有關注的投資者界面自動刷新。
-
在線協作工具(如 Google Docs):一個用戶編輯內容,其他用戶的視圖實時更新。
-
前端框架(如 Vue、React)的數據綁定:數據變化驅動視圖渲染。
-
一個主界面由好幾個子界面垂直布局組成, 數據源的變更,子界面的數據將實時變化(頁面由還幾個一級標題頁面,為了解耦和代碼管理按照標題差分類結構)
3. 狀態監控與報警
場景:監控系統狀態變化,并觸發相關響應(如日志、報警、資源調整)。
案例:
- 服務器 CPU 使用率超過閾值時,觸發郵件報警、記錄日志、自動擴容。
- 物聯網設備(如傳感器)數據異常時,通知用戶和管理系統。
4. 游戲開發中的事件系統
場景:游戲內事件(如角色死亡、任務完成)需要觸發多模塊響應。
案例:
- 玩家生命值降為 0 時,觸發 UI 更新死亡動畫、保存進度、播放音效。
- 成就系統:當玩家達成特定條件(如擊殺 100 個敵人),解鎖成就并推送通知。
5. 配置或參數動態更新
場景:系統配置變更后,相關組件需動態調整行為,無需重啟。
案例:
- 修改系統主題顏色,所有界面組件自動切換配色。
- 動態調整日志級別,實時生效。
6. 分布式系統中的一致性保證
場景:多個服務需要根據核心服務狀態變化保持一致性。
案例:
- 電商系統中,訂單狀態變為“已支付”時,通知庫存服務扣減庫存、物流服務生成運單。
- 分布式緩存失效:當緩存數據更新,通知所有節點清除舊緩存。
觀察者模式的優勢
- 解耦:被觀察者與觀察者之間松耦合,可獨立擴展。
- 靈活性:動態添加/移除觀察者,符合開閉原則。
- 一致性:確保所有依賴對象在狀態變化時同步更新。
注意事項
- 性能問題:觀察者過多或通知邏輯復雜時,可能影響性能。
- 循環依賴:避免觀察者間相互觸發導致死循環。
- 內存泄漏:某些語言(如 Java)需手動注銷觀察者,防止對象無法回收。
何時選擇觀察者模式?
- 一個對象的變化需要通知其他對象,且具體通知對象未知或可變。
- 需要減少對象間的直接依賴,提升代碼復用性和可維護性。
- 跨層級或跨模塊通信,尤其是事件驅動的系統架構。
推模式代碼
#include <iostream>
#include <vector>
#include <memory>// 觀察者接口
class Observer {
public:virtual void update(float temperature, float humidity) = 0; // 推送具體數據virtual ~Observer() = default;
};// 被觀察者(氣象站)
class WeatherStation {
private:std::vector<Observer*> observers;float temperature;float humidity;public:void addObserver(Observer* observer) {observers.push_back(observer);}void removeObserver(Observer* observer) {// 實際代碼中需要更安全的刪除邏輯observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void setMeasurements(float temp, float hum) {temperature = temp;humidity = hum;notifyObservers();}private:void notifyObservers() {for (auto observer : observers) {observer->update(temperature, humidity); // 推送數據}}
};// 具體觀察者(顯示屏)
class Display : public Observer {
public:void update(float temperature, float humidity) override {std::cout << "顯示屏更新 - 溫度: " << temperature << "°C, 濕度: " << humidity << "%\n";}
};int main() {WeatherStation station;Display display;station.addObserver(&display);station.setMeasurements(25.5f, 60.0f); // 數據變化時自動推送return 0;
}
拉模式代碼
#include <iostream>
#include <vector>
#include <memory>// 觀察者接口
class Observer {
public:virtual void update() = 0; // 不推送數據,觀察者自行拉取virtual ~Observer() = default;
};// 被觀察者(氣象站)
class WeatherStation {
private:std::vector<Observer*> observers;float temperature;float humidity;public:void addObserver(Observer* observer) {observers.push_back(observer);}void removeObserver(Observer* observer) {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void setMeasurements(float temp, float hum) {temperature = temp;humidity = hum;notifyObservers();}// 觀察者通過接口拉取數據float getTemperature() const { return temperature; }float getHumidity() const { return humidity; }private:void notifyObservers() {for (auto observer : observers) {observer->update(); // 僅通知,不傳遞數據}}
};// 具體觀察者(顯示屏)
class Display : public Observer {
private:WeatherStation& station; // 觀察者持有被觀察者的引用以拉取數據public:Display(WeatherStation& station) : station(station) {}void update() override {float temp = station.getTemperature();float hum = station.getHumidity();std::cout << "顯示屏更新 - 溫度: " << temp << "°C, 濕度: " << hum << "%\n";}
};int main() {WeatherStation station;Display display(station); // 觀察者需要持有被觀察者的引用station.addObserver(&display);station.setMeasurements(25.5f, 60.0f); // 數據變化時通知觀察者return 0;
}
和發布訂閱者模式的區別:
觀察者模式(Observer Pattern)和發布-訂閱模式(Pub-Sub Pattern)是兩種常用于解耦對象間通信的設計模式,但它們的設計思想和應用場景有顯著區別。以下是兩者的核心差異和對比:
1. 核心機制與角色關系
特性 | 觀察者模式 | 發布-訂閱模式 |
---|---|---|
通信方式 | 直接通信:被觀察者(Subject)直接通知觀察者(Observer)。 | 間接通信:發布者(Publisher)和訂閱者(Subscriber)通過**中介層(Broker/Event Bus)**交互。 |
角色關系 | - 被觀察者(Subject) - 觀察者(Observer) | - 發布者(Publisher) - 訂閱者(Subscriber) - 中介層(Broker) |
耦合度 | 較高:觀察者需要直接注冊到被觀察者,依賴其接口。 | 極低:發布者和訂閱者彼此無感知,僅依賴中介層和事件類型。 |
事件路由 | 被觀察者自行管理觀察者列表,并決定通知邏輯。 | 中介層負責事件的路由、過濾和廣播(例如按主題、標簽或內容匹配)。 |
2. 典型代碼結構對比
(1) 觀察者模式
// 被觀察者(Subject)
class WeatherStation {
private:std::vector<Observer*> observers;
public:void addObserver(Observer* observer) { /*注冊觀察者*/ }void notifyObservers() {for (auto obs : observers) {obs->update(temperature); // 直接調用觀察者的接口}}
};// 觀察者(Observer)
class Display : public Observer {
public:void update(float temp) override { /*更新顯示*/ }
};
(2) 發布-訂閱模式
// 中介層(Broker)
class MessageBroker {
private:std::unordered_map<std::string, std::vector<Subscriber*>> topicSubscribers;
public:void subscribe(const std::string& topic, Subscriber* sub) { /*按主題訂閱*/ }void publish(const std::string& topic, const std::string& message) {for (auto sub : topicSubscribers[topic]) {sub->onMessage(message); // 通過中介層轉發消息}}
};// 訂閱者(Subscriber)
class User : public Subscriber {
public:void onMessage(const std::string& msg) override { /*處理消息*/ }
};
3. 關鍵區別
維度 | 觀察者模式 | 發布-訂閱模式 |
---|---|---|
通信方向 | 單向:被觀察者 → 觀察者 | 多向:發布者 → 中介層 → 訂閱者(支持多對多通信) |
動態性 | 觀察者需顯式注冊到具體被觀察者 | 訂閱者通過中介層動態訂閱事件類型(如主題、頻道) |
擴展性 | 新增事件類型需修改被觀察者接口 | 新增事件類型只需在中介層注冊,無需修改發布者或訂閱者 |
適用場景 | 對象間一對多的簡單依賴關系(如GUI事件、狀態同步) | 復雜的多對多通信、跨系統解耦(如微服務、消息隊列) |
典型應用 | - 按鈕點擊事件監聽 - 數據模型更新UI | - 新聞訂閱系統 - 分布式系統的異步通信 - 實時聊天室 |
4. 場景示例
(1) 觀察者模式適用場景
-
GUI事件處理:
按鈕(被觀察者)被點擊時,直接通知所有注冊的監聽器(觀察者)執行操作。button.addClickListener(&logListener); // 日志監聽器 button.addClickListener(&uiUpdater); // 界面更新監聽器
-
游戲狀態同步:
角色血量變化時,通知UI組件、音效模塊、成就系統更新。
(2) 發布-訂閱模式適用場景
-
新聞訂閱系統:
用戶(訂閱者)訂閱“科技”主題,發布者發布新聞時,中介層將消息推送給所有訂閱者。broker.subscribe("科技", &user1); // 用戶訂閱主題 broker.publish("科技", "AI新突破!"); // 發布者發送消息
-
微服務通信:
訂單服務(發布者)發布“訂單創建”事件,庫存服務(訂閱者)接收事件并扣減庫存。
5. 總結:何時選擇哪種模式?
模式 | 選擇條件 |
---|---|
觀察者模式 | - 對象間關系簡單(一對多) - 需要直接控制通知邏輯 - 實時性要求高 |
發布-訂閱模式 | - 系統需要解耦(多對多) - 動態事件類型和訂閱關系 - 跨組件或跨系統通信 |
6. 常見誤區
-
誤區1:發布-訂閱是觀察者模式的“升級版”。
糾正:兩者解決不同問題。觀察者模式強調直接通信,發布-訂閱強調間接通信和解耦。 -
誤區2:中介層(如
TalkNotifier
)的存在即表示發布-訂閱模式。
糾正:觀察者模式中的Subject
也可以視為簡單中介,但發布-訂閱的中介層更獨立且支持復雜路由。 -
誤區3:發布-訂閱必須異步。
糾正:發布-訂閱可以實現為同步或異步,而觀察者模式通常是同步的。