在正式介紹觀察者模式前,我們先引用生活中的小例子來模擬觀察者,先對觀察者模式有一個整體的感覺。
現實模擬
報紙和雜志的故事。
我們看看報紙和雜志的訂閱是怎么一回事:
- 報紙的任務就是出版報紙
- 我們向某家報社訂閱報紙,只要他們有新報紙出版,就會給你送來,只要你是他們的訂戶,你就會一直得到新報紙
- 當你們不想再看報紙的時候,向報社取消訂閱,他們就不會再送報紙來,你也不會再收到報紙
- 只要報社還在運營,就會有人向他們訂閱或者取消報紙
這其實就可以理解為是一種觀察者模式。報社出版者被認為是觀察者模式中的Subject,訂閱報紙的人被認為是觀察者模式中的Observer。具體的觀察者模式的subject和observer我們后面會介紹。
訂閱者通常有很多個,他們訂閱或者取消需要通知出版者。出版者當報紙有更新時,就會把新報紙一起推送給訂閱者,所有訂閱者都會收到出版社的所有更新。
再舉個常見的例子,我們常見的手機app,網易新聞或者其他類。只要我們安裝了這個這個應用,并在app設置接收應用的消息通知,那么當app有新消息通知時,我們就會收到新消息。這里,我們用戶就是觀察者,app就是Subject。
觀察者模式定義
觀察者模式是設計模式中很常用的一個模式。
比較嚴格的解釋是:** 觀察者模式定義了對象之間的一對多的依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知并自動更新。**
跟圖中的例子一樣,主題和觀察者定義了一對多的關系。觀察者依賴于此主題,只要主題狀態一有變化,觀察者就會被通知。
觀察者模式的類圖可以很好的觀察者模式的設計思想
觀察者的設計方式有很多種,但其中實現Subject和observer接口的設計方式是最常用的、
Subject的接口有三個方法,分別是注冊觀察者,移除觀察者和通知觀察者。對象通過Subject接口注冊成為觀察者,同事也可以通過它從解除觀察者的身份,也就是之前例子中的取消訂閱報紙。
每個Subject通??梢杂泻芏鄠€觀察者
具體的Subject對象需要實現Subject接口的三個方法,其中notify方法是用于當狀態發生變化時,來通知觀察者update,里面一般要調用觀察者接口的update方法。
所有的觀察者都需要實現Observer接口,并實現其中的update方法,以便當主題狀態發生變化,觀察者得到主題的通知。用于Subject具體實現類的notify方法的調用。
具體的Observer都需要繼承至接口,同時他們必須注冊到具體的Subject對象,以成為一個觀察者,并得到更新。
觀察者實現的設計原則
** 觀察者模式提供了一種對象設計,讓主題和觀察者之間松耦合 **
關于觀察者的一切,主題只需要知道觀察者實現了某個接口也就是Observer接口,主題不需要知道觀察者的具體的實現類是誰,做了些什么或者其他任何細節,主題都不需要知道。
任何時候我們都可以增加新的觀察者,因為主題唯一依賴的東西是一個實現Observer接口的對象列表,所以我們可以隨時增加觀察者。事實上,在運行時我們可以用新的觀察者取代現有的觀察者,主題不會受任何影響。同樣的,也可以在任何時候刪除觀察者。
當有新的類型的觀察者出現時,主題的代碼不會發生修改。假如我們有個新的具體類需要當觀察者,我們不需要為了兼容新類型而修改主題的代碼,所需要的只是在新的類里實現此觀察者的接口,然后注冊為觀察者即可。
這里體現了一個設計原則就是** 為了交互對象之間的松耦合設計而努力 **
爭取讓對象之間的互相依賴降到最低
代碼實現
我們考慮這樣一個問題:實現一個氣象站監測應用。
有三個部分,氣象站(獲取實際氣象數據的裝置),weatherData對象(追蹤來自氣象站的數據,并更新布告板)和布告板(顯示目前天氣的狀況給用戶看)
我們要做到的就是建立一個應用,利用weatherdata對象獲取數據,并更新三個布告板。
我們對氣象站的初步設計圖:
根據觀察者設計了一個類圖,接下來我們實現這個類圖。從建立接口開始,
package com.liu.itf;
public interface Subject {
public void registerObserver(Observers o);
public void removeObserver(Observers o);
public void notifyObserver();
}
package com.liu.itf;
public interface Observers {
public void update(float temp, float humidity, float pressure);
}
package com.liu.itf;
public interface DisplayElement {
public void display();
}
接下來在weatherdata類中實現Subject接口
package com.liu.model;
import java.util.ArrayList;
import com.liu.itf.Observers;
import com.liu.itf.Subject;
public class WeatherData implements Subject {
private ArrayList<Observers> observers;
private float temperature;
private float pressure;
private float humidity;
public WeatherData() {
// TODO Auto-generated constructor stub
observers = new ArrayList<Observers>();
}
@Override
public void registerObserver(Observers o) {
// TODO Auto-generated method stub
observers.add(o);
}
@Override
public void removeObserver(Observers o) {
// TODO Auto-generated method stub
int i = observers.indexOf(o);
if(i>=0) {
observers.remove(o);
}
}
@Override
public void notifyObserver() {
// TODO Auto-generated method stub
for(int i=0;i<observers.size();i++) {
Observers observer = (Observers)observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObserver();
}
public void setMeasurements(float temperature, float humidity,float pressure) {
this.humidity = humidity;
this.temperature = temperature;
this.pressure = pressure;
measurementsChanged();
}
}
布告板類作為觀察者實現觀察者接口和display接口
package com.liu.view;
import com.liu.itf.DisplayElement;
import com.liu.itf.Observers;
import com.liu.itf.Subject;
public class CurrentConditionDisplay implements DisplayElement, Observers {
private float temperature;
private float pressure;
private float humidity;
private Subject weatherData;
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
// TODO Auto-generated method stub
this.temperature = temp;
this.humidity = humidity;
display();
}
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
}
編寫一個測試類:
import com.liu.model.WeatherData;
import com.liu.view.CurrentConditionDisplay;
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
weatherData.setMeasurements(80, 45, 30.4f);
}
}
小結
- 觀察者定義了對象之間一對多的關系。
- 主題用一個共同的接口來更新觀察者
- 觀察者和主題之間用松耦合的方式連接,主題不知道觀察者的細節,只知道觀察者實現了觀察者接口