深入理解觀察者模式:構建松耦合的交互系統

在軟件開發中,我們經常遇到這樣的場景:一個對象的狀態變化需要通知其他多個對象,并且這些對象需要根據變化做出相應的反應。比如,用戶界面中的數據變化需要實時反映到多個圖表上,或者電商系統中的庫存變化需要通知訂單系統和推薦系統。觀察者模式(Observer Pattern)正是為解決這類問題而生的經典設計模式。

一、觀察者模式概述

觀察者模式是一種行為型設計模式,它定義了對象之間的一種一對多的依賴關系,當一個對象(稱為"主題"或"被觀察者")的狀態發生改變時,所有依賴于它的對象(稱為"觀察者")都會得到通知并自動更新。

1.1 模式結構

觀察者模式包含四個核心角色:

  1. Subject(主題/被觀察者)

    • 維護一個觀察者列表

    • 提供添加和刪除觀察者的方法

    • 定義通知觀察者的方法

  2. Observer(觀察者接口)

    • 定義更新接口,用于接收主題通知

  3. ConcreteSubject(具體主題)

    • 實現主題接口

    • 存儲具體狀態

    • 狀態改變時通知所有觀察者

  4. ConcreteObserver(具體觀察者)

    • 實現觀察者接口

    • 維護對具體主題的引用(可選)

    • 實現更新邏輯以保持與主題狀態一致

1.2 UML類圖

+----------------+       +----------------+
|    Subject     |       |    Observer    |
+----------------+       +----------------+
| +attach(o)     |<>-----| +update()      |
| +detach(o)     |       +----------------+
| +notify()      |               ^
+----------------+               |^                         ||                         |
+----------------+       +----------------+
| ConcreteSubject|       | ConcreteObserver|
+----------------+       +----------------+
| +getState()    |       | +update()      |
| +setState()    |       +----------------+
+----------------+

二、觀察者模式的實現

2.1 經典實現

讓我們通過一個新聞發布系統的例子來展示觀察者模式的經典實現:

// 觀察者接口
interface NewsObserver {void update(String news);
}// 主題接口
interface NewsPublisher {void subscribe(NewsObserver observer);void unsubscribe(NewsObserver observer);void notifyObservers();
}// 具體主題
class CNN implements NewsPublisher {private List<NewsObserver> subscribers = new ArrayList<>();private String latestNews;public void setLatestNews(String news) {this.latestNews = news;notifyObservers();}@Overridepublic void subscribe(NewsObserver observer) {subscribers.add(observer);}@Overridepublic void unsubscribe(NewsObserver observer) {subscribers.remove(observer);}@Overridepublic void notifyObservers() {for (NewsObserver observer : subscribers) {observer.update(latestNews);}}
}// 具體觀察者
class NewsSubscriber implements NewsObserver {private String name;public NewsSubscriber(String name) {this.name = name;}@Overridepublic void update(String news) {System.out.println(name + " received breaking news: " + news);}
}// 使用示例
public class Main {public static void main(String[] args) {CNN cnn = new CNN();NewsObserver subscriber1 = new NewsSubscriber("John");NewsObserver subscriber2 = new NewsSubscriber("Alice");cnn.subscribe(subscriber1);cnn.subscribe(subscriber2);cnn.setLatestNews("Global tech summit announces AI breakthroughs");}
}

2.2 Java內置實現

Java標準庫中提供了java.util.Observable類和java.util.Observer接口,可以簡化觀察者模式的實現:

import java.util.Observable;
import java.util.Observer;// 被觀察者
class WeatherStation extends Observable {private float temperature;public void setTemperature(float temperature) {this.temperature = temperature;setChanged(); // 標記狀態已改變notifyObservers(temperature); // 通知觀察者}
}// 觀察者
class TemperatureDisplay implements Observer {private String location;public TemperatureDisplay(String location) {this.location = location;}@Overridepublic void update(Observable o, Object arg) {System.out.printf("[%s] Current temperature: %.1f°C\n", location, (Float)arg);}
}// 使用示例
public class WeatherApp {public static void main(String[] args) {WeatherStation station = new WeatherStation();Observer display1 = new TemperatureDisplay("Living Room");Observer display2 = new TemperatureDisplay("Bedroom");station.addObserver(display1);station.addObserver(display2);// 模擬溫度變化station.setTemperature(23.5f);station.setTemperature(22.8f);}
}

三、觀察者模式的深入分析

3.1 推模型 vs 拉模型

觀察者模式有兩種主要的實現方式:

  1. 推模型(Push Model)

    • 主題將詳細的變化數據推送給觀察者

    • 觀察者被動接收數據

    • 實現簡單,但可能推送不必要的數據

  2. 拉模型(Pull Model)

    • 主題僅通知觀察者狀態已改變

    • 觀察者主動從主題拉取所需數據

    • 更靈活,觀察者可以決定需要什么數據

    • 但增加了觀察者與主題的耦合

3.2 線程安全問題

在多線程環境中使用觀察者模式時需要考慮:

  1. 主題狀態變更和通知的原子性

  2. 觀察者列表的線程安全

  3. 觀察者更新方法的執行線程

解決方案包括:

  • 使用synchronized關鍵字

  • 使用并發集合如CopyOnWriteArrayList

  • 使用事件總線或消息隊列

3.3 觀察者模式的優缺點

優點

  1. 松耦合:主題和觀察者之間抽象耦合,可以獨立變化

  2. 動態關系:可以在運行時動態添加或刪除觀察者

  3. 廣播通信:支持一對多的通知機制

  4. 開閉原則:新增觀察者無需修改主題代碼

缺點

  1. 通知順序不可控:觀察者接收通知的順序不確定

  2. 性能問題:大量觀察者或復雜更新邏輯可能導致性能瓶頸

  3. 循環依賴:不當使用可能導致觀察者之間循環調用

  4. 內存泄漏:觀察者未正確注銷可能導致內存泄漏

四、觀察者模式的應用場景

觀察者模式廣泛應用于以下場景:

4.1 GUI事件處理

幾乎所有現代GUI框架都基于觀察者模式:

// Java Swing示例
JButton button = new JButton("Click me");
button.addActionListener(e -> {System.out.println("Button was clicked!");
});

4.2 發布-訂閱系統

消息隊列(如Kafka、RabbitMQ)是觀察者模式的擴展實現:

// 偽代碼示例
MessageBroker broker = new MessageBroker();// 發布者
broker.publish("news", "Breaking news content");// 訂閱者
broker.subscribe("news", message -> {System.out.println("Received news: " + message);
});

4.3 數據監控與報警系統

class ServerMonitor extends Observable {private double cpuUsage;public void checkStatus() {// 模擬獲取CPU使用率cpuUsage = Math.random() * 100;if (cpuUsage > 80) {setChanged();notifyObservers(cpuUsage);}}
}class AlertSystem implements Observer {@Overridepublic void update(Observable o, Object arg) {System.out.printf("ALERT: High CPU usage detected: %.1f%%\n", (Double)arg);}
}

4.4 MVC架構

模型(Model)變化時自動更新視圖(View):

class UserModel extends Observable {private String name;public void setName(String name) {this.name = name;setChanged();notifyObservers();}
}class UserView implements Observer {@Overridepublic void update(Observable o, Object arg) {UserModel model = (UserModel)o;System.out.println("View updated: User name is now " + model.getName());}
}

五、觀察者模式的變體與相關模式

5.1 發布-訂閱模式

觀察者模式的增強版,通過消息代理解耦發布者和訂閱者:

  • 發布者不知道訂閱者的存在

  • 支持更靈活的消息過濾和路由

  • 通常用于分布式系統

5.2 事件總線/事件驅動架構

集中管理事件和監聽器:

// 偽代碼示例
EventBus bus = new EventBus();// 發布事件
bus.post(new OrderCreatedEvent(order));// 訂閱事件
@Subscribe
public void handleOrderCreated(OrderCreatedEvent event) {// 處理訂單創建事件
}

5.3 中介者模式

與觀察者模式的區別:

  • 中介者知道所有組件

  • 組件之間不直接通信

  • 更適合復雜的交互關系

六、最佳實踐與注意事項

  1. 避免過度使用:不是所有狀態變化都需要觀察者模式

  2. 考慮性能影響:大量觀察者或復雜更新邏輯可能成為瓶頸

  3. 處理異常:觀察者更新時拋出異常不應影響其他觀察者

  4. 防止內存泄漏:及時移除不再需要的觀察者

  5. 考慮線程安全:多線程環境下的正確同步

  6. 文檔化依賴關系:明確記錄主題與觀察者之間的關系

七、現代語言中的觀察者模式

7.1 JavaScript中的觀察者模式

// 簡單的觀察者實現
class Observable {constructor() {this.observers = [];}subscribe(fn) {this.observers.push(fn);}unsubscribe(fn) {this.observers = this.observers.filter(subscriber => subscriber !== fn);}notify(data) {this.observers.forEach(observer => observer(data));}
}// 使用示例
const newsObservable = new Observable();const logger = news => console.log(`New article: ${news}`);
const notifier = news => alert(`Breaking news: ${news}`);newsObservable.subscribe(logger);
newsObservable.subscribe(notifier);newsObservable.notify("JavaScript ES2023 features released");

7.2 React中的觀察者模式

React的狀態管理和Hooks本質上是觀察者模式的實現:

import React, { useState, useEffect } from 'react';function UserProfile() {const [user, setUser] = useState(null);// 模擬數據訂閱useEffect(() => {const subscription = userService.subscribe(user => {setUser(user);});return () => subscription.unsubscribe();}, []);return (<div>{user && <p>Welcome, {user.name}</p>}</div>);
}

八、總結

觀察者模式是構建松耦合、可擴展系統的強大工具。通過定義清晰的依賴關系,它使得對象之間的交互更加靈活和可維護。從GUI事件處理到復雜的分布式系統,觀察者模式的應用無處不在。

然而,正如我們討論的,觀察者模式并非銀彈。在實際應用中,我們需要根據具體場景權衡其優缺點,考慮性能影響、線程安全等問題。現代框架和語言通常提供了更高級的抽象(如響應式編程、事件總線等),但這些概念的核心仍然是觀察者模式。

掌握觀察者模式不僅能幫助我們更好地理解和設計軟件系統,還能提升我們對現代框架和編程范式背后原理的認識。希望本文能為你深入理解和應用這一經典設計模式提供有價值的參考。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88855.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88855.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88855.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

React強大且靈活hooks庫——ahooks入門實踐之常用場景hook

什么是 ahooks&#xff1f; ahooks 是一個 React Hooks 庫&#xff0c;提供了大量實用的自定義 hooks&#xff0c;幫助開發者更高效地構建 React 應用。其中場景類 hooks 是 ahooks 的一個重要分類&#xff0c;專門針對特定業務場景提供解決方案。 安裝 ahooks npm install …

Qt常用控件之QWidget(一)

Qt常用控件之QWidget&#xff08;一&#xff09;1.QWidget2.enabled屬性2.geometry&#x1f31f;&#x1f31f;hello&#xff0c;各位讀者大大們你們好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列專欄&#xff1a;【Qt的學習】 &#x1f4dd;&#x1f4dd;本…

AIOT開發選型:行空板 K10 與 M10 適用場景與選型深度解析

前言 隨著人工智能和物聯網技術的飛速發展&#xff0c;越來越多的開發者、學生和愛好者投身于創意項目的構建。 在眾多的開發板中&#xff0c;行空板 K10 和 M10 以其獨特的優勢脫穎而出。 本文旨在為讀者提供一份詳盡的行空板 K10 和 M10 對比分析&#xff0c;從適用場景、…

redis匯總筆記

語雀完整版&#xff1a; https://www.yuque.com/g/mingrun/embiys/calwqx/collaborator/join?tokensLcLnqz5Rv8hOKEB&sourcedoc_collaborator# 《Redis筆記》 Redis 一般問題 Redis內存模型&#xff08;I/O多路模型&#xff09;多路復用IO如何解釋 為什么Redis要使用單線…

STM32用PWM驅動步進電機

硬件介紹&#xff1a;連線&#xff1a;注意這里stp連的是pwm脈沖&#xff0c;dir連的是方向到時候代碼pwm波形就是從這里來的&#xff0c;具體接線根據你的代碼來注意要點&#xff1a;步進電機和舵機驅動是不一樣的&#xff0c;它是根據步長來移動的&#xff0c;所以要開一個中…

力扣25.7.10每日一題——重新安排會議得到最多空余時間 II

Description 今天這道題和昨天類似&#xff0c;只是允許順序變化。 Solution 把會議區間視作桌子&#xff0c;空余時間視作空位&#xff0c;我們要把一個桌子移到別的空位中。 初步想法是枚舉桌子&#xff0c;找一個長度大于等于桌子長度的空位移過去。看上去&#xff0c;找…

IP報文分片與重組原理及實現分析

IP報文分片與重組原理及實現分析 引用&#xff1a; ppp/net/packet/IPFragment.hppp/net/packet/IPFragment.cpp 1. IP分片原理 當IP數據包大小超過MTU&#xff08;最大傳輸單元&#xff09;時&#xff0c;路由器/主機將其分割為多個片段傳輸&#xff0c;每個片段包含&…

[python]在drf中使用drf_spectacular

安裝drf_spectacular 文檔 pypi鏈接:https://pypi.org/project/drf-spectacular/ 文檔鏈接:https://drf-spectacular.readthedocs.io/en/latest/readme.html 安裝步驟 在環境中添加 pip install drf-spectacular在setting的INSTALLED_APPS中添加 INSTALLED_APPS [# ALL…

【Datawhale AI 夏令營】 用AI做帶貨視頻評論分析(二)

5.預訓練模型跑分 回顧賽題 回顧賽題任務 挑戰與難點&#xff1a; 標注數據少 ——> 半監督學習 or 數據增強 聚類分析噪點影響嚴重 回顧Baseline 問題&#xff1a; TF-IDF無法捕捉以下語義。聚類分析粗糙&#xff0c;未評估聚類質量。 提升方案&#xff1a; 分類任務…

SPSSPRO:數據分析市場SaaS挑戰者的戰略分析

目錄 第一部分&#xff1a;執行摘要 第二部分&#xff1a;平臺解構&#xff1a;產品、架構與用戶體驗 2.1 SaaS范式轉移&#xff1a;架構與起源 2.2 功能能力&#xff1a;分析師的工具箱 2.3 “智能分析”的價值主張 第三部分&#xff1a;市場滲透與受眾細分 3.1 目標用戶…

低版本hive(1.2.1)UDF實現清除歷史分區數據

目標&#xff1a;通過UDF實現對表歷史數據清除 入參&#xff1a;表名、保留天數N 一、pom文件 <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.…

C++中頂層const與底層const

在C中&#xff0c;const關鍵字用于定義常量&#xff0c;但它在指針和引用上下文中會產生兩種不同的常量性&#xff1a;頂層const&#xff08;top-level const&#xff09;和底層const&#xff08;low-level const&#xff09;。理解它們的區別是避免編譯錯誤和提高代碼質量的關…

“SRP模型+”多技術融合在生態環境脆弱性評價模型構建、時空格局演變分析與RSEI指數生態質量評價

集成云端、桌面端等環境&#xff0c;結合遙感云計算、GIS空間分析&#xff0c;R語言統計分析的優勢&#xff0c;以分析生態環境脆弱性的時空演變為主線。通過本課程的學習&#xff0c;您將掌握&#xff1a;第一&#xff0c;收集各類指標數據&#xff0c;構建的“生態壓力度-生態…

算法學習筆記:17.蒙特卡洛算法 ——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

在計算機科學和數學領域&#xff0c;蒙特卡洛算法&#xff08;Monte Carlo Algorithm&#xff09;以其獨特的隨機抽樣思想&#xff0c;成為解決復雜問題的有力工具。從圓周率的計算到金融風險評估&#xff0c;從物理模擬到人工智能&#xff0c;蒙特卡洛算法都發揮著不可替代的作…

Tortoise 設置

如何關閉 Windows 下 TortoiseGit 任務欄里窗口標題的分支顯示 一、引言 TortoiseGit 是一個專為團隊協作設計的 Git 圖形化客戶端&#xff0c;旨在解決版本控制中常見的問題&#xff0c;如沖突、回滾、歷史查看等。本文檔是 TortoiseGit 的使用手冊前言部分&#xff0c;旨在向…

[論文閱讀] 人工智能 + 軟件工程 | AI助力軟件可解釋性:從用戶評論到自動生成需求與解釋

AI助力軟件可解釋性&#xff1a;從用戶評論到自動生成需求與解釋 Automatic Generation of Explainability Requirements and Software Explanations From User ReviewsarXiv:2507.07344 Automatic Generation of Explainability Requirements and Software Explanations From …

C語言---自定義類型(上)(結構體類型)

結構體結構體的定義與聲明結構體其實和數組一樣&#xff0c;都是一些值的集合&#xff0c;只不過數組是一系類相同類型的值&#xff0c;而結構體里邊的成員可以是不同的數據類型。關于它的聲明&#xff0c;所用到的關鍵字是struct。聲明的語法如下&#xff1a;struct 結構體名{…

Java觀察者模式實現方式與測試方法

一、實現方式 自定義實現 通過手動定義Subject和Observer接口&#xff0c;實現一對多依賴關系&#xff1a; // 觀察者接口 public interface Observer {void update(float temp, float humidity, float pressure); } // 主題接口 public interface Subject {void registerObser…

leetGPU解題筆記(1)

1.題面 題目要求 向量加法 實現一個程序&#xff0c;在GPU上對兩個包含32位浮點數的向量執行逐元素加法。該程序應接受兩個長度相等的輸入向量&#xff0c;并生成一個包含它們和的輸出向量。 實現要求 禁止使用外部庫 solve函數簽名必須保持不變 最終結果必須存儲在向量C中 示例…

5. JVM 的方法區

1. JVM介紹和運行流程-CSDN博客 2. 什么是程序計數器-CSDN博客 3. java 堆和 JVM 內存結構-CSDN博客 4. 虛擬機棧-CSDN博客 5. JVM 的方法區-CSDN博客 6. JVM直接內存-CSDN博客 7. JVM類加載器與雙親委派模型-CSDN博客 8. JVM類裝載的執行過程-CSDN博客 9. JVM垃圾回收…