深入理解 Java 觀察者模式:原理、實現與應用

? ? ? ?在軟件開發領域,設計模式堪稱開發者智慧的凝練結晶,它們為解決各類常見編程難題提供了行之有效的方案。觀察者模式(Observer Pattern)作為行為型設計模式的重要一員,在處理對象間依賴關系與事件通知方面表現卓越。本文將深入剖析 Java 中的觀察者模式,涵蓋其核心概念、實現途徑、優劣之處及適用場景,助力讀者全方位掌握并靈活運用這一關鍵設計模式。?

一、觀察者模式核心概念?

(一)定義與核心思想?

觀察者模式定義了對象間的一對多依賴關系,當某一對象(被觀察者,Subject)的狀態發生變動時,所有依賴于它的對象(觀察者,Observer)都會收到通知并自動更新。其核心在于解耦觀察者與被觀察者,讓二者借助抽象接口進行交互,而非直接耦合,以此提升系統的靈活性與可擴展性。?

(二)角色劃分?

觀察者模式主要涉及四個關鍵角色:?

  1. 抽象被觀察者(Subject):定義了添加、移除觀察者以及通知觀察者的接口或抽象類,維護著一個觀察者列表,當自身狀態變化時,負責通知列表中的所有觀察者。?
  2. 具體被觀察者(Concrete Subject):實現抽象被觀察者的接口,具體負責管理自身狀態,狀態變化時,調用通知方法告知觀察者。?
  3. 抽象觀察者(Observer):定義了用于接收被觀察者通知的更新接口,收到通知后執行相應操作。?
  4. 具體觀察者(Concrete Observer):實現抽象觀察者的更新接口,通常持有對具體被觀察者的引用,以便在收到通知時獲取被觀察者狀態并處理。?

二、Java 中觀察者模式的實現方式?

在 Java 里,實現觀察者模式主要有兩種常見途徑:一種是借助 JDK 內置的觀察者相關類與接口,另一種則是自定義實現。接下來將分別詳細闡述。?

(一)JDK 內置的觀察者模式實現?

Java 的 java.util 包提供了 Observable 類和 Observer 接口,方便快速實現觀察者模式。?

1. Observable 類?

Observable 類是抽象被觀察者的具體實現,具備以下主要方法:?

  • addObserver(Observer o):向觀察者列表中添加一個觀察者。?
  • deleteObserver(Observer o):從觀察者列表中移除一個觀察者。?
  • notifyObservers():通知所有已注冊的觀察者,但調用此方法前需先調用setChanged()方法,用以標記被觀察者狀態已改變。?
  • notifyObservers(Object arg):帶參數的通知方法,將參數傳遞給觀察者。?
  • setChanged():設置內部標志,表明被觀察者狀態已改變,只有調用此方法后再調用notifyObservers(),觀察者才會收到通知。?
  • clearChanged():清除狀態改變標志。?

2. Observer 接口?

Observer 接口定義了觀察者的更新方法,僅有一個方法:?

  • update(Observable o, Object arg):當被觀察者狀態變化并通知觀察者時,該方法被調用。其中,o是發出通知的被觀察者對象,arg是傳遞給觀察者的參數(若有)。?

3. 使用步驟示例?

以簡單的天氣數據監測系統為例,展示如何運用 JDK 內置的觀察者模式。?

步驟 1:創建具體被觀察者(WeatherData)?

?

import java.util.Observable;?

?

public class WeatherData extends Observable {?

private float temperature;?

private float humidity;?

private float pressure;?

?

public void setWeatherData(float temperature, float humidity, float pressure) {?

this.temperature = temperature;?

this.humidity = humidity;?

this.pressure = pressure;?

// 設置狀態改變標志?

setChanged();?

// 通知所有觀察者?

notifyObservers();?

// 或者傳遞具體參數?

// notifyObservers(new WeatherInfo(temperature, humidity, pressure));?

}?

?

// 獲取溫度、濕度、氣壓的方法...?

}?

?

步驟 2:創建具體觀察者(CurrentConditionsDisplay)?

?

import java.util.Observable;?

import java.util.Observer;?

?

public class CurrentConditionsDisplay implements Observer {?

private float temperature;?

private float humidity;?

private Observable observable;?

?

public CurrentConditionsDisplay(Observable observable) {?

this.observable = observable;?

// 將當前觀察者添加到被觀察者的列表中?

observable.addObserver(this);?

}?

?

@Override?

public void update(Observable o, Object arg) {?

if (o instanceof WeatherData) {?

WeatherData weatherData = (WeatherData) o;?

this.temperature = weatherData.getTemperature();?

this.humidity = weatherData.getHumidity();?

display();?

}?

}?

?

private void display() {?

System.out.println("當前天氣狀況:溫度 " + temperature + "℃,濕度 " + humidity + "%");?

}?

}?

?

步驟 3:客戶端使用?

?

public class Client {?

public static void main(String[] args) {?

WeatherData weatherData = new WeatherData();?

CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);?

?

// 模擬天氣數據變化?

weatherData.setWeatherData(25.0f, 60.0f, 1013.0f);?

weatherData.setWeatherData(28.0f, 55.0f, 1010.0f);?

}?

}?

?

4. 注意事項?

  • Observable 類是具體類而非接口,這在一定程度上限制了其擴展性,因為 Java 不支持多繼承,若一個類已繼承其他類,便無法再繼承 Observable 類。?
  • 調用 notifyObservers () 方法前,務必先調用 setChanged () 方法,否則觀察者不會收到通知。這是由于 Observable 類內部通過布爾變量 changed 判斷是否通知觀察者,setChanged () 方法將其設為 true,notifyObservers () 方法調用后會將其設為 false(除非使用帶參數版本且參數為 null)。?

(二)自定義實現觀察者模式?

盡管 JDK 提供了內置的觀察者實現,但在某些場景下,我們可能期望更靈活地定義抽象被觀察者和抽象觀察者接口,或者避免依賴 JDK 中的 Observable 類(比如被觀察者需繼承其他類時)。此時,可自定義實現觀察者模式。?

1. 定義抽象觀察者接口(Observer)?

?

public interface Observer {?

void update(Object data);?

}?

?

2. 定義抽象被觀察者接口(Subject)?

?

public interface Subject {?

void registerObserver(Observer observer);?

void removeObserver(Observer observer);?

void notifyObservers(Object data);?

}?

?

3. 實現具體被觀察者(ConcreteSubject)?

?

import java.util.ArrayList;?

import java.util.List;?

?

public class ConcreteSubject implements Subject {?

private List<Observer> observers = new ArrayList<>();?

private Object data;?

?

@Override?

public void registerObserver(Observer observer) {?

observers.add(observer);?

}?

?

@Override?

public void removeObserver(Observer observer) {?

observers.remove(observer);?

}?

?

@Override?

public void notifyObservers(Object data) {?

this.data = data;?

for (Observer observer : observers) {?

observer.update(data);?

}?

}?

?

public void setData(Object data) {?

notifyObservers(data);?

}?

}?

?

4. 實現具體觀察者(ConcreteObserver)?

?

public class ConcreteObserver implements Observer {?

private String name;?

?

public ConcreteObserver(String name) {?

this.name = name;?

}?

?

@Override?

public void update(Object data) {?

System.out.println("觀察者" + name + "接收到數據:" + data);?

}?

}?

?

5. 客戶端使用示例?

?

public class CustomObserverClient {?

public static void main(String[] args) {?

ConcreteSubject subject = new ConcreteSubject();?

ConcreteObserver observer1 = new ConcreteObserver("A");?

ConcreteObserver observer2 = new ConcreteObserver("B");?

?

subject.registerObserver(observer1);?

subject.registerObserver(observer2);?

?

subject.setData("新數據來了!");?

subject.removeObserver(observer2);?

subject.setData("再次更新數據!");?

}?

}?

?

(三)兩種實現方式的對比?

?

特性?

JDK 內置實現?

自定義實現?

抽象程度?

使用具體類 Observable 和接口 Observer,Observable 設計存在局限性(如非接口)?

完全自定義抽象接口,可依需求靈活設計接口方法?

擴展性?

因 Observable 是具體類,若被觀察者需繼承其他類則無法使用,擴展性受限?

被觀察者實現自定義 Subject 接口,可自由繼承其他類,擴展性更佳?

依賴關系?

依賴 java.util 包中的類和接口?

不依賴 JDK 特定類,可在更廣泛環境中使用?

學習成本?

簡單易用,適用于快速實現簡單觀察者模式場景?

需手動定義接口和實現,適用于復雜場景或高度定制需求?

?

三、觀察者模式的優缺點?

(一)優點?

  1. 解耦觀察者和被觀察者:觀察者與被觀察者通過抽象接口交互,彼此無需了解對方具體實現,降低代碼耦合度。被觀察者狀態變化時,觀察者自動接收通知,無需主動查詢,提升系統靈活性。?
  2. 支持廣播通信:被觀察者能同時通知多個觀察者,實現一對多通信模式,契合消息推送、狀態變更通知等事件廣播場景。?
  3. 提高可維護性和擴展性:觀察者與被觀察者分離,新增或修改觀察者行為不影響被觀察者代碼,反之亦然。系統更易維護和擴展,符合開閉原則(對擴展開放,對修改關閉)。?

(二)缺點?

  1. 通知順序問題:被觀察者通知觀察者時,通常按注冊順序依次調用更新方法。若觀察者間存在依賴關系或對通知順序有特定要求,可能需額外處理,否則易導致意外結果。?
  2. 潛在的性能問題:若觀察者數量眾多,或每個觀察者的更新方法執行耗時較長,被觀察者通知所有觀察者時可能耗時過多,引發性能下降,極端情況下甚至導致線程阻塞。?
  3. 過度使用可能導致復雜度增加:觀察者模式雖能解耦對象,但系統中過度使用會使對象間依賴關系復雜,難以追蹤和維護。尤其當多個被觀察者和觀察者存在復雜交互時,可能形成難以管理的網狀結構。?

四、觀察者模式的適用場景?

(一)當一個對象的狀態變化需要通知多個對象時?

這是觀察者模式最典型的應用場景。比如在股票交易系統中,某只股票價格變動時,需通知所有關注該股票的用戶;新聞訂閱系統里,有新新聞發布時,要通知所有訂閱該新聞頻道的用戶。?

(二)當系統需要實現事件驅動機制時?

觀察者模式可用于實現事件監聽與處理機制。以 GUI 編程為例,按鈕點擊事件、窗口關閉事件等,都可通過觀察者模式實現。用戶觸發事件(相當于被觀察者狀態變化)時,注冊的事件監聽器(相當于觀察者)接收通知并執行相應處理邏輯。?

(三)當需要解耦具有依賴關系的對象時?

若兩個或多個對象存在一對多的依賴關系,即一個對象變化影響多個對象,使用觀察者模式可將它們解耦,使其通過抽象接口交互,而非直接依賴具體實現,提升系統靈活性與可維護性。?

(四)當需要實現數據的實時更新和同步時?

在分布式系統或實時數據處理系統中,常需將數據變化實時同步到多個客戶端或模塊。觀察者模式能便捷實現數據實時更新,數據源(被觀察者)數據變化時,所有訂閱該數據源的客戶端(觀察者)自動接收通知并更新自身數據。?

五、實際應用案例分析?

(一)Swing 中的事件處理?

在 Java Swing GUI 開發中,觀察者模式廣泛應用于事件處理。例如,用戶點擊按鈕時,按鈕對象(被觀察者)通知所有注冊的動作監聽器(觀察者)。動作監聽器實現 ActionListener 接口(相當于抽象觀察者接口),其中的 actionPerformed 方法(相當于 update 方法)在按鈕點擊時被調用,執行相應事件處理邏輯。?

(二)Spring Framework 中的事件機制?

Spring 框架提供基于觀察者模式的事件發布 - 訂閱機制。通過 ApplicationEvent 類(抽象事件,相當于被觀察者狀態變化通知)和 ApplicationListener 接口(觀察者接口),開發者可自定義事件和事件監聽器。事件發生時,Spring 容器將事件發布給所有注冊監聽器,監聽器執行相應處理邏輯。該機制在 Spring Boot 的自動配置、事務管理等模塊均有應用。?

(三)消息中間件中的訂閱 - 發布模式?

消息中間件(如 Kafka、RabbitMQ 等)的訂閱 - 發布模式(Publish/Subscribe Pattern)與觀察者模式極為相似。生產者(相當于被觀察者)發布消息到主題(Topic),消費者(相當于觀察者)訂閱主題并接收消息。盡管訂閱 - 發布模式通常借助消息代理(Broker)作為中介,而觀察者模式中被觀察者和觀察者可直接交互,但二者核心思想均為一對多依賴關系和事件通知。?

六、總結與最佳實踐?

觀察者模式是極為實用的設計模式,通過解耦觀察者和被觀察者,實現靈活的事件通知機制,適用于多種場景。在 Java 中,既可用 JDK 內置的 Observable 和 Observer 快速實現簡單觀察者模式,也可通過自定義接口實現更靈活、貼合需求的模式。?

運用觀察者模式時,需留意以下最佳實踐:?

  1. 合理設計抽象接口:抽象被觀察者和抽象觀察者的接口應盡量通用,涵蓋所有可能操作,避免頻繁修改接口。?
  2. 控制觀察者數量和更新邏輯:避免注冊過多觀察者,或在觀察者更新方法中執行耗時操作,防止性能問題。?
  3. 處理循環依賴問題:若觀察者和被觀察者存在循環依賴(如觀察者更新時修改被觀察者狀態,導致再次通知觀察者),需謹慎處理,避免陷入無限循環。?
  4. 結合具體場景選擇實現方式:依據項目具體需求,選擇 JDK 內置實現或自定義實現。若需高度靈活性和擴展性,自定義實現更佳;若場景簡單,追求快速實現,JDK 內置實現更便捷。?

深入理解觀察者模式的原理、實現方式和適用場景,并遵循最佳實踐,開發者便能在軟件開發中靈活運用該模式,提升代碼質量與系統可維護性。隨著技術持續發展,觀察者模式也在不斷演進,與責任鏈模式、策略模式等結合,用以解決更復雜的問題。因此,持續學習和實踐設計模式,對提升軟件開發能力意義重大。

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

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

相關文章

網絡原理 TCP/IP

1.應用層 1.1自定義協議 客戶端和服務器之間往往進行交互的是“結構化”數據&#xff0c;網絡傳輸的數據是“字符串”“二進制bit流”&#xff0c;約定協議的過程就是把結構化”數據轉成“字符串”或“二進制bit流”的過程. 序列化&#xff1a;把結構化”數據轉成“字符串”…

2025年5月HCIP題庫(帶解析)

某個ACL規則如下:則下列哪些IP地址可以被permit規則匹配&#xff1a; rule 5 permit ip source 10.0.2.0 0.0.254.255 A、10.0.4.5 B、10.0.5.6 C、10.0.6.7 D、10.0.2.1 試題答案&#xff1a;A;C;D 試題解析&#xff1a; 10.0.2.000001010.00000000.00000010.0000000…

【Redis | 基礎總結篇 】

目錄 前言&#xff1a; 1.Redis的介紹&#xff1a; 2.Redis的類型與命令&#xff1a; 3.Redis的安裝&#xff1a; 3.1.Windows版本 3.2.Linux版本 4.在java中使用Redis&#xff1a; 4.1.介紹 4.2.Jedis 4.3.Spring Data Redis 前言&#xff1a; 本篇主要講述了Redis的…

38.前端代碼拆分

因為前端代碼之前是一體編寫的&#xff0c;所以為了方便對代碼進行了拆分 之前是這樣的&#xff1a; 為了更加規范&#xff0c;所以拆分成vue、util、store、api等部分&#xff1a; css&#xff1a; store: 拆分后的大致界面為&#xff1a; 其實還有點別扭需要后續再調整

tinyrenderer筆記(Shader)

tinyrenderer個人代碼倉庫&#xff1a;tinyrenderer個人練習代碼 前言 現在我們將所有的渲染代碼都放在了 main.cpp 中&#xff0c;然而在 OpenGL 渲染管線中&#xff0c;渲染的核心邏輯是位于 shader 中的&#xff0c;下面是 OpenGL 的渲染管線&#xff1a; 藍色是我們可以自…

C++高性能內存池

目錄 1. 項目介紹 1. 這個項目做的是什么? 2. 該項目要求的知識儲備 2. 什么是內存池 1. 池化技術 2. 內存池 3. 內存池主要解決的問題 4.malloc 3. 先設計一個定長的內存池 4.高并發內存池 -- 整體框架設計 5. 高并發內存池 -- thread cache 6. 高并發內存池 -- …

LintCode407-加一,LintCode第479題-數組第二大數

第407題: 描述 給定一個非負數&#xff0c;表示一個數字數組&#xff0c;在該數的基礎上1&#xff0c;返回一個新的數組。 該數字按照數位高低進行排列&#xff0c;最高位的數在列表的最前面. 樣例 1&#xff1a; 輸入&#xff1a;[1,2,3] 輸出&#xff1a;[1,2,4] 樣例 …

SMT貼片鋼網精密設計與制造要點解析

內容概要 SMT貼片鋼網作為電子組裝工藝的核心載體&#xff0c;其設計與制造質量直接影響焊膏印刷精度及產品良率。本文系統梳理了鋼網全生命周期中的15項關鍵技術指標&#xff0c;從材料選擇、結構設計到工藝控制構建完整技術框架。核心要點涵蓋激光切割精度的微米級調控、開口…

OpenCV進階操作:角點檢測

文章目錄 一、角點檢測1、定義2、檢測流程1&#xff09;輸入圖像2&#xff09;圖像預處理3&#xff09;特征提取4&#xff09;角點檢測5&#xff09;角點定位和標記6&#xff09;角點篩選或后處理&#xff08;可選&#xff09;7&#xff09;輸出結果 二、Harris 角點檢測&#…

江蘇正力新能Verify認知能力測評筆試已通知 | SHL測評題庫預測題 | 華東同舟求職講求職

江蘇正力新能入職筆試通知&#xff0c;Verify&#xff08;認知能力測評&#xff09;&#xff0c;用時約46分鐘&#xff0c;其中正式測試部分計時36分鐘&#xff1b;時間到了試卷會自動提交&#xff0c;請合理安排答題時間&#xff01;前面有10分鐘練習時間&#xff0c;可以略過…

在若依里創建新菜單

首先打開左側菜單欄的系統管理&#xff0c;然后點擊菜單管理 可以點擊左上角的新增&#xff0c;也可以點擊右側對應目錄的新增 這里我選擇了右側的新增&#xff0c;即在系統管理目錄下新增菜單 其中的組件路徑就是寫好的頁面的路徑 &#xff08;從views的下一級開始寫即可&…

【AI知識庫云研發部署】RAGFlow + DeepSeek

可以分成兩臺機器部署,一臺gpu,一臺cpu,cpu的機器運行ragflow的主程序,使用模型時才訪問gpu。當然全部在一臺機器上部署是完全ok的。全文沒有復雜的環境問題 gpu 安裝screen:yum install screen 配置ollama: 下載官方安裝腳本并執行: curl -fsSL https://ollama.co…

Java后端開發day40--異常File

&#xff08;以下內容全部來自上述課程&#xff09; 異常 異常&#xff1a;異常就是代表程序出現的問題 1. 異常的分類 1.1 Error 代表的是系統級別的錯誤&#xff08;屬于嚴重問題&#xff09; 系統一旦出現問題&#xff0c;sun公司會把這些錯誤封裝成Error對象。 Error…

算法思想之深度優先搜索(DFS)、遞歸以及案例(最多能得到多少克黃金、精準核酸檢測、最富裕的小家庭)

深度優先搜索&#xff08;DFS&#xff09;、遞歸 深度優先搜索&#xff08;Depth First Search&#xff0c;DFS&#xff09;是一種用于遍歷或搜索樹或圖的算法。在 DFS 算法中&#xff0c;從起始節點開始&#xff0c;沿著一條路徑盡可能深地訪問節點&#xff0c;直到到達葉子節…

Spark,HDFS客戶端操作

hadoop客戶端環境準備 找到資料包路徑下的Windows依賴文件夾&#xff0c;拷貝hadoop-3.1.0到非中文路徑&#xff08;比如d:\hadoop-3.1.0&#xff09; ① 打開環境變量 ② 在下方系統變量中新建HADOOP_HOME環境變量,值就是保存hadoop的目錄。 效果如下&#xff1a; ③ 配置Pa…

共享會議室|物聯網解決方案:打造高效、智能的會議空間!

在數字化轉型的浪潮下&#xff0c;企業、園區、公共機構的會議室面臨諸多痛點&#xff0c;如何通過物聯網技術實現會議室資源的智能調度、環境設備的自動化控制以及用戶體驗的全面升級&#xff1f;本文將結合行業實踐與技術方案&#xff0c;探討基于物聯網的共享會議室解決方案…

ts bug 找不到模塊或相應類型的聲明,@符有紅色波浪線

解決方法&#xff1a;在env.d.ts文件中添加以下代碼&#xff0c;這段代碼是一個 TypeScript 的聲明文件&#xff0c;用于讓 TypeScript 知道如何處理 Vue 單文件組件&#xff08;.vue 文件&#xff09;的導入。 /// <reference types"vite/client" /> // 聲明…

端口隔離基本配置

1.top圖 2.交換機配置 # sysname sw1 # vlan batch 10 # interface GigabitEthernet0/0/1port link-type trunkport trunk allow-pass vlan 10 # interface GigabitEthernet0/0/2port link-type trunkport trunk allow-pass vlan 10sys sw2 # vlan batch 10 # interface Giga…

Android View#post()源碼分析

文章目錄 Android View#post()源碼分析概述onCreate和onResume不能獲取View的寬高post可以獲取View的寬高總結 Android View#post()源碼分析 概述 在 Activity 中&#xff0c;在 onCreate() 和 onResume() 中是無法獲取 View 的寬高&#xff0c;可以通過 View#post() 獲取 Vi…

SecureCrt設置顯示區域橫列數

1. Logical rows //邏輯行調顯示區域高度的 一般超過50就全屏了 2. Logical columns //邏輯列調顯示區域寬度的 3. Scrollback buffer //緩沖區大小