HeadFirst design pattern筆記-觀察者模式

來源:互聯網
上載者:User

在實現觀察者模式前,先看一個天氣預報系統的需求,WeatherData對象負責擷取天氣資料(溫度,濕度,壓力),然後顯示在公告板上,公告板可以有很多種類型。

不使用設計模式的做法:

public class WeatherData {//氣象測量更新資料時,此方法調用public void measurementsChanged() {//當前溫度float temp = getTemperature();//當前濕度float humidity = getHumidity();//當前壓力float pressure = getPressure();//更新公告板1currentConditionsDisplay.update(temp, humidity, pressure);//公告板2currentConditionsDisplay2.update(temp, humidity, pressure);//公告板3currentConditionsDisplay3.update(temp, humidity, pressure);.......}}

這種方法的缺點是顯而易見的:

針對具體實現編程而不是介面,每個公告板都要加入到WeatherData類中,如果有公告板的變動,需要更改代碼。

無法在運行時動態地增加/修改公告板

破壞了WeatherData的封裝

公告板的update方法可以抽象成一個公用介面

……

要解決這些問題,這時候使用觀察者模式就很方便了。

觀察者定義了對象間的一對多依賴,當一個對象(主題)狀態改變時,它的所有依賴者(觀察者)都會收到通知並且自動更新,觀察者移除後,不再收到通知。

松耦合:2個對象可以互動,但是不太清楚對方的細節,使得對象之間的互相依賴降到了最低

觀察者模式讓主題和觀察者之間松耦合。有新的觀察者出現時,主題的代碼不需要修改。觀察者在主題中註冊就行了,主題也不關心有多少觀察者,只是發送通知給所有實現了觀察者介面的對象。

觀察者模式的簡要類圖:


其中,主題Subjcet和觀察者Observer是1對多的關係

Subject介面的3個操作,registerObserver用來註冊觀察者對象,removeObserver用來移除觀察者對象,notifyObservers用來通知所有的觀察者。

Observer介面的update方法用於接收通知,其實就是用於被Subject實作類別的notifyObservers方法調用

現在看一下觀察者模式下天氣預報系統的實現:

WeatherData對象需要在資料變化時通知所有的公告板,根據上面的概念,WeatherData要實現Subjcet介面,公告板要實現Observer介面。

Subjcet介面:

public interface Subject {public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObservers();}

Observer介面:

public interface Observer {public void update(float temp, float humidity, float pressure);}

WeatherData:

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<Observer>();}@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {int i = observers.indexOf(o);if (i > 0)observers.remove(i);}@Overridepublic void notifyObservers() {for (int i = 0; i < observers.size(); i++) {Observer observer = observers.get(i);observer.update(temperature, humidity, pressure);}}public void measurementsChanged() {notifyObservers();}/** * 類比氣象資料改變 * */public void setMeasurements(float temperature, float humidity,float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();}}

其中一種公告板CurrentConditionsDisplay:

/** * 公告板,其中一個觀察者 * */public class CurrentConditionsDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private float pressure;//主題的引用private Subject weatherData;public CurrentConditionsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}@Overridepublic void display() {System.out.println("temperature="+temperature+";humidity="+humidity+";pressure="+pressure);}@Overridepublic void update(float temp, float humidity, float pressure) {this.temperature = temp;this.humidity = humidity;this.pressure = pressure;display();}//移除觀察者private void quit() {weatherData.removeObserver(this);}}

Displayment介面無關緊要,用於顯示資料而已

public interface DisplayElement {public void display();}

可以看到,實現Subject介面的WeatherData類中維護了一個Observer介面的Arraylist,以便動態維護註冊和取消的觀察者。

一旦天氣資料變化,調用measurementsChanged方法,measurementsChanged方法調用notifyObservers,notifyObservers其實就是遍曆在WeatherData類中註冊的所有Observer(公告板),然後調用它們的update方法。這是一個“推”的過程,Observer們不需要手動去Subject那裡取資料。這就實現了“當一個對象(主題)狀態改變時,它的所有依賴者(觀察者)都會收到通知並且自動更新,觀察者移除後,不再收到通知。”的概念

現在調用測試代碼:

public class WeatherStation {public static void main(String[] args) {WeatherData wData = new WeatherData();CurrentConditionsDisplay curDis = new CurrentConditionsDisplay(wData);wData.setMeasurements(80, 65, 30.4f);wData.setMeasurements(81, 61, 33.4f);wData.setMeasurements(82, 62, 31.4f);}}

控制台輸出:

temperature=80.0;humidity=65.0;pressure=30.4
temperature=81.0;humidity=61.0;pressure=33.4
temperature=82.0;humidity=62.0;pressure=31.4

JAVA內建的觀察者模式

JAVA中Observable類和Observer介面已經幫我們實現好了觀察者模式,而且可以選擇用“推”或者“拉”的方式。

推和拉的區別是觀察者是否主動去主題那邊取資料,這在代碼中是通過notifyObservers方法是否傳參體現的,如果傳參則是推,不傳參,更新的資料需要觀察者調用主題的get方法得到,這樣就是拉了。

改動以上的代碼,注釋部分顯示了改動的地方,特別要注意的是和之前的程式對比,它們實現的介面的變化!

WeatcherData.java

import java.util.HashMap;import java.util.Map;import java.util.Observable;/** * 擷取氣象資料,通知觀察者 * */public class WeatherData extends Observable {//implements Subject {//private ArrayList<Observer> observers;private float temperature;private float humidity;private float pressure;public WeatherData() {//observers = new ArrayList<Observer>();}//@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(i);//}////@Override//public void notifyObservers() {//for (int i = 0; i < observers.size(); i++) {//Observer observer = observers.get(i);//observer.update(temperature, humidity, pressure);//}//}//public void measurementsChanged() {//notifyObservers();//}public void measurementsChanged() {/*notifyObservers是Observable內建的方法,使用之前要調用setchanged,觀察者才能收到通知*/setChanged();//現在把溫度等資訊封裝在map中傳給觀察者Map<String, Object> map = new HashMap<String, Object>();map.put("temperature", temperature);map.put("humidity", humidity);map.put("pressure", pressure);notifyObservers(map);}/** * 類比氣象資料改變 * */public void setMeasurements(float temperature, float humidity,float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();}}

CurrentConditionsDisplay.java

import java.util.Map;import java.util.Observable;/** * 公告板,其中一個觀察者 * */public class CurrentConditionsDisplay implements DisplayElement, java.util.Observer{//Observer, DisplayElement {private float temperature;private float humidity;private float pressure;//主題的引用//private Subject weatherData;//Observable對象的引用private Observable observerable;//public CurrentConditionsDisplay(WeatherData weatherData) {//this.weatherData = weatherData;//weatherData.registerObserver(this);//}public CurrentConditionsDisplay(Observable observable) {this.observerable = observable;observerable.addObserver(this);}@Overridepublic void display() {System.out.println("temperature="+temperature+";humidity="+humidity+";pressure="+pressure);}//@Override//public void update(float temp, float humidity, float pressure) {//this.temperature = temp;//this.humidity = humidity;//this.pressure = pressure;//display();//}//使用java.util.Observer的update方法@Overridepublic void update(Observable o, Object arg) {//檢測是否是WeatherData這個主題傳遞的資訊if(o instanceof WeatherData) {//這是拉的方式//WeatherData weatherData = (WeatherData)o;//this.temperature = weatherData.getXXXX//這是推的方式Map<String, Object> map = (Map<String, Object>)arg;this.temperature = (float) map.get("temperature");this.humidity = (float) map.get("humidity");this.pressure = (float) map.get("pressure");display();}}}

測試類別不變,運行結果:

temperature=80.0;humidity=65.0;pressure=30.4
temperature=81.0;humidity=61.0;pressure=33.4
temperature=82.0;humidity=62.0;pressure=31.4

用JAVA內建的實現可以方便很多,但是也有缺點,比如Observable是一個類而不是像第一個程式那樣實現的Subjcet介面,這樣靈活性下降。

很多地方都用到了觀察者模式,比如Listener。

舉個例子,android的按鈕監聽

setOnClickListener(new OnClickListener(){

控制項對象其實是“主題”,OnClickListener類是觀察者,重寫其onClick方法相當於重寫update方法。按鈕被點擊時,註冊過的觀察者,即OnClickListener的“update方法”onCLick方法會被調用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.