引言
觀察者模式可以說是JDK中使用最多的模式之一,這個模式可以讓你的物件隨時隨地的了解想要知道的情況,你還可以讓某個物件在運行時決定是否要繼續通知;觀察者模式的定義為:
定義物件間一種一對多的依賴關系,使得當每一個物件改變狀態,則所有依賴于它的物件都會得到通知并自動更新
從定義就可以看出,觀察者模式主要就是兩部分:主題(Subject)和觀察者(Observer)
為了更清楚的了解觀察者模式,下面將會用案例說明觀察者模式;
天氣預報專案
今天一個新的專案交到了你的手上,這個專案是一個天氣預報公司的,他們想要
- 將每天測量到的溫度,濕度,氣壓等等以公告的形式發布出去
- 設計開放型 API,便于其他第三方也能接入氣象站獲取資料
- 而且天氣預報最基本的要求是準時,不能開始下雨了才把下雨的預報發出來,所以很重要的一點是:每當天氣預報更新的時候,要實時通知給第三方;
通過需求分析可以提煉出關鍵一點:公布天氣資訊的“布告板”可以有多個,這些布告板全部從天氣預報公司獲取天氣資訊;
大致是下圖的意思:

再加上實時更新的需求,就是對標觀察者模式;
上面圖可以等同于下面這個:

所以只需要確定好觀察者模式的兩個主要組成:主題(Subject)和觀察者(Observer)就好了;
很明顯,主題物件就是天氣預報公司,觀察者就是一系列第三方網站(的布告板);
接下來就是如何用方法將觀察者和主題聯系起來呢?
對于觀察者來說,需要的方法就是一個:
- 接受主題的輸入(update)
對于主題來說,需要以下功能:
- registerObserver() 注冊
- removeObserver() 移除
- notifyObservers() 通知注冊的用戶
那么就可以簡單嘗試畫出UML類圖了:

大致就是這樣,Subject是主題的介面,Observer是觀察者的介面,
可能你會覺得上面明明就說的那么簡單,怎么這一下子多了好幾個東西
其實這樣子的代碼耦合度更低,而且思路非常簡單,下面就來通過代碼實作一下就明白了;
主題:
// 主題介面
public interface Subject {
public void registerObserver(Observer o); // 注冊觀察者
public void removeObserver(Observer o); // 移除觀察者
public void notifyObserver(); // 通知觀察者
}
// 實作主題介面,該類為天氣預報公司
import java.util.ArrayList;
public class WeatherData implements Subject {
private ArrayList<Observer> observers; // 觀察者陣列,存放所有觀察者
private float temperature; // 溫度
private float humidity; // 濕度
private float pressure; // 壓力
public WeatherData() {
observers = new ArrayList<>();
}
// 注冊觀察者
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
// 移除觀察者
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(o);
}
}
// 通知觀察者
@Override
public void notifyObserver() {
for (var i : observers) {
i.update(this.temperature, this.humidity, this.pressure);
}
}
// 資訊更新
public void measurementsChanged() {
notifyObserver();
}
// 設定天氣
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged(); // 設定完成天氣就已經改變了
}
}
觀察者:
// 觀察者
public interface Observer {
public void update(float temp, float humidity, float pressure); // 更新布告板
public void display(); // 展示布告板
}
// 天氣預報公司自己的布告板
public class CurrentConditionsDisplay implements Observer{
private float temperature; // 溫度
private float humidity; // 濕度
private float pressure; // 壓力
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
@Override
public void display() {
System.out.println("當前溫度:" + this.temperature +
"當前濕度:" + this.humidity + "當前壓力:" + this.pressure);
}
}
// 百度網站的溫度布告板
public class BaiDuDisplay implements Observer{
private float temperature; // 溫度
private float humidity; // 濕度
private float pressure; // 壓力
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
@Override
public void display() {
System.out.println("百度溫度:" + this.temperature +
"百度濕度:" + this.humidity + "百度壓力:" + this.pressure);
}
}
還可以繼續增加布告板,這里就列舉這兩個了;
測驗執行代碼:
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
Observer current = new CurrentConditionsDisplay(); // 自己網站的觀察者
Observer baidu = new BaiDuDisplay(); // 百度觀察者
weatherData.registerObserver(current); // 注冊觀察者
weatherData.registerObserver(baidu); // 注冊觀察者
weatherData.setMeasurements(11, 22, 33);
}
}
運行結果:
當前溫度:11.0當前濕度:22.0當前壓力:33.0
百度溫度:11.0百度濕度:22.0百度壓力:33.0
其實一看代碼,還是兩大部分,所以對于觀察者模式,主要思考的是主題和觀察者的關系就可以了;
使用了觀察者模式后,代碼有以下好處:
- 觀察者模式設計后,會以集合的方式來管理用戶(Observer),包括注冊,移除和通知
- 增加觀察者(即成一個新的公告板),就不需要去修改核心類 WeatherData 的代碼,遵守 ocp (開閉)原則
Java內置觀察者模式
JDK中提供了 java.util.Observable 實作類和 java.util.Observer 介面,這是Java內置的觀察者模式,可以直接來用,先簡單對比介紹一下:
- Observable 的作用和地位等價于前面的Subject
- Observable 是類,不是介面,類中已經實作了核心的方法 ,即管理 Observer 的方法 add… delete … notify…
- Observer 介面的作用等價于前面的 Observer介面, 都有update
- Observable類 和 Observer介面 的使用方法和前面講過的一樣,只是 Observable 是類,通過繼承來實作觀察者模式
下面我將用內置觀察者模式支持來重寫一下上面的代碼實作相同的功能;
主題:
import java.util.Observable;
// 直接繼承Observable類
public class WeatherData extends Observable {
private float temperature; // 溫度
private float humidity; // 濕度
private float pressure; // 壓力
// 溫度改變通知觀察者
public void measurementsChanged() {
setChanged(); // 確定已經改變
notifyObservers();
}
// 設定溫度
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
觀察者:
// 天氣預報公司自己的布告板
import java.util.Observable;
import java.util.Observer;
// 實作了Observer介面
public class CurrentConditionsDisplay implements Observer{
private float temperature; // 溫度
private float humidity; // 濕度
private float pressure; // 壓力
public void display() {
System.out.println("當前溫度為:" + this.temperature + " 當前濕度為:"
+ this.humidity + " 當前壓力為:" + this.pressure);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
this.pressure = weatherData.getPressure();
display();
}
}
}
// 百度網站的溫度布告板
import java.util.Observable;
import java.util.Observer;
// 實作了Observer介面
public class BaiDuDisplay implements Observer {
private float temperature; // 溫度
private float humidity; // 濕度
private float pressure; // 壓力
public void display() {
System.out.println("百度溫度為:" + this.temperature + " 百度濕度為:"
+ this.humidity + " 百度壓力為:" + this.pressure);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
this.pressure = weatherData.getPressure();
display();
}
}
}
測驗執行代碼:
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(); // 主題
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); // 觀察者
BaiDuDisplay baiDuDisplay = new BaiDuDisplay(); // 百度觀察者
weatherData.addObserver(currentConditionsDisplay); // 增添觀察者
weatherData.addObserver(baiDuDisplay);// 增添觀察者
weatherData.setMeasurements(11, 22, 33);
}
}
運行結果:
百度溫度為:11.0 百度濕度為:22.0 百度壓力為:33.0
當前溫度為:11.0 當前濕度為:22.0 當前壓力為:33.0
是不是有很多相同的地方,歸根結底都是使用的觀察者模式,所以只要知道觀察者模式由什么組成,什么時候用,那么你就真正了解了觀察者模式;
但是設計模式可不是隨便看看這倆代碼就能會用的,這也是設計模式不好學的一點,還是需要大量的代碼練習去體會,最后就能靈活運用;
注:在JDK中JavaBeans和Swing都實作了觀察者模式,感興趣可以嘗試一下;
總結
總結一下觀察者模式:
- 觀察者模式定義了物件之間一對多的關系
- 主題通過一個共同的介面來更新觀察者
- 觀察者和主題之間用松耦合的方式結合,主題不知道觀察者的細節,只知道觀察者實作了觀察者介面
- 當有多個觀察者時,不能依賴特定的通知次序
最后在這里展示一下觀察者模式的UML類圖:

在這篇文章中的例子的代碼只是為了展示觀察者模式,但是并不是最優的代碼,一個好的代碼可能要用到多種設計模式,這也是設計模式的一個難點;
設計模式的學習沒有什么技巧,只能在敲代碼的程序中不斷去體會,可能某一天你就會不自覺的使用設計模式,還是要多敲代碼多踩坑多思考,希望我們一起努力!!
歡迎大家的點評!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/351036.html
標籤:其他
下一篇:Java-類和物件
