設計模式之觀察者模式

從一個氣象監測應用說起:

我們想做一個氣象應用,有一個WeatherData對象,該對象由氣象站的各種感應裝置提供溫度,溼度,氣壓數據 來初始化。同時我們還要設立3個佈告板,來根據天氣數據 顯示不同的內容。分別是:目前狀況,氣象統計,簡單的預報。 同時我們還希望可以擴展該接口,即第三方可以根據數據來實現自己的佈告板。

背景已經交代清楚了,這其實是一個一對多的關係,即多個佈告板全都依賴WeatherData對象,根據WeatherData對象做出自己的改變。下面是一個錯誤的示範

class WeatherData{
	float temp;
	float humidity;
	float pressure;
	WeatherData(float temp,float humidity,float pressure){
		this.temp = temp;
		this.humidity = humidity;
		this.pressure = pressure;
	}
	public void measurementsChanged(){
		temp = getTemp();
		humidity  = getHumidity();
		pressure = getPressure();
		currentDonditionsDisplay.update(temp,humidity,pressure);
		staticsDisplay.update(temp,humidity,pressure);
		forecastDisplay.update(temp,humidity,pressure);
	}
}
雖然它能夠滿足大部分條件,但是它不具備擴展性,每次添加新的佈告板時,我們都需要修改代碼,我們是針對接口編程而不是針對具體實現編程,佈告板沒有實現一個共同的接口,而且我們無法在運行的時候動態的刪除或添加布告板(只能在measurementsChanged()方法中刪除對該佈告板的更新。所以爲了搞定這個問題,我們引出觀察者模式:

認識觀察者模式:

觀察者模式和報紙和雜誌的訂閱很像,報社出版報紙,向某家報社訂閱報紙,只要他們有新報紙出版,就會給你送來,只要你是他們的訂戶,你就會一直收到新報紙。當你不想在看報紙時,取消訂閱,他們便不會再送新報紙給你。只要報社還在運營,就會一直有人向他們訂閱報紙或取消訂閱報紙。

出版者 + 訂閱者 = 觀察者模式

我們可以將他們改下名稱, 出版者改爲“主題”,訂閱者改爲 “觀察者”(《Head First 設計模式》裏面是這樣子的)。

觀察者模式的定義:

觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都將會收到通知並自動更新。

好了,我們可以根據這個模式來解決我們的實際問題了。

在我們的問題中,佈告板會隨着WeatherData的變化而得到更新,所以很明顯WeatherData是主題,而佈告板是觀察者。實現觀察者模式的方法不只一種,但是以包含Subjec與Observer接口的最爲常見,之後會講到JAVA中自帶的觀察者模式。下面是具體實現:



import java.util.*;

interface Subject {
	void registerObserver(Observer o);

	void removerObserver(Observer o);

	void notifyObservers();
}

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

interface Displayment {// 展示接口
	void display();
}

class WeatherData implements Subject {

	private float temp;
	private float humidity;
	private float pressure;
	private List<Observer> observers = new ArrayList<Observer>();

	WeatherData(float temp, float humidity, float pressure) {
		this.temp = temp;
		this.humidity = humidity;
		this.pressure = pressure;
	}

	@Override
	public void registerObserver(Observer o) {
		// TODO 自動生成的方法存根
		observers.add(o);
	}

	@Override
	public void removerObserver(Observer o) {
		// TODO 自動生成的方法存根
		int i = observers.indexOf(o);
		if (i >= 0)
			observers.remove(i);
	}

	@Override
	public void notifyObservers() {
		// TODO 自動生成的方法存根
		for (Observer o : observers)
			o.update(temp, humidity, pressure);
	}

	public void measurementsChanged(float temp, float humidity, float pressure) {
		this.temp = temp;
		this.humidity = humidity;
		this.pressure = pressure;
		notifyObservers();
	}

}

class CurrentConditionDisplay implements Observer, Displayment {
	private float temp;
	private float humidity;
	private float pressure;
	private Subject subject;

	public CurrentConditionDisplay(Subject subject) {
		// TODO 自動生成的構造函數存根
		this.subject = subject;
		subject.registerObserver(this);
	}

	public CurrentConditionDisplay() {
		// TODO 自動生成的構造函數存根
	}

	@Override
	public void update(float temp, float humidity, float pressure) {
		// TODO 自動生成的方法存根
		this.temp = temp;
		this.humidity = humidity;
		this.pressure = pressure;
		display();
	}

	@Override
	public void display() {
		// TODO 自動生成的方法存根
		System.out.println("當前氣溫:" + temp + "當前溼度:" + humidity + "當前氣壓:"
				+ pressure);

	}

}

public class WeatherDataTest {
	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData(27.2f, 30.0f, 110f);
		weatherData.registerObserver(new CurrentConditionDisplay());
		weatherData.measurementsChanged(24.0f, 22.2f, 23.2f);

	}
}


此實現有缺點。一個觀察者可以有多個主題,觀察者可根據主題來實現不同的update(),例如我們可以將update()改成update(Subject subject,Object obj)後面的是Object是主題給觀察者的數據對象。

其實在這裏觀察者得到數據的方法是由主題推送的,觀察者無法通過自身得到更新的數據,我們可以在主題中實現觀察者訪問數據的接口

//在主題中提供數據接口
public float getTemp(){
         return temp;
}
public float getHumiditu(){
         return humidity;
}
public float getPressure(){
         return pressure;
}
接下來我們用JAVA內置的支持來實現這個問題。

java內置的觀察者模式:

JAVA API有內置的觀察者模式,在java.util包中,包含最基本的Observer接口和Observable類(主題的另一種說法,“可觀察者”),裏面的方法請參考JDK文檔,在此我們就直接實現:

import java.util.*;
interface Displayment{//展示接口
	void display();
}
class WeatherData extends Observable{
	private float temp;
	private float humidity;
	private float pressure;
	public WeatherData(){};
	public void measurementsChanged(float temp, float humidity, float pressure){
		this.temp = temp;
		this.humidity = humidity;
		this.pressure = pressure;
		setChanged();//java自帶的,讓我們更新的時候有更多的彈性。
		notifyObservers();
	}
	public float getTemp(){
        return temp;
	}
	public float getHumidity(){
        return humidity;
	}
	public float getPressure(){
        return pressure;
	}
	
}
class CurrentConditionDisplay implements Observer,Displayment{
	private float temp;
	private float humidity;
	private float pressure;
	private Observable observable;
	public CurrentConditionDisplay(Observable observable) {
		// TODO 自動生成的構造函數存根
		this.observable = observable;
		observable.addObserver(this);	
	}
	public void display() {
		// TODO 自動生成的方法存根
		System.out.println("當前氣溫:"+temp+"當前溼度:"+humidity+"當前氣壓:"+pressure);
			
	}
	@Override
	public void update(Observable o, Object data) {
		// TODO 自動生成的方法存根
		if(o instanceof WeatherData){
			WeatherData weatherData = (WeatherData)o;
			this.temp = weatherData.getTemp();
			this.humidity = weatherData.getHumidity();
			this.pressure = weatherData.getPressure();
			display();
		}	
	}
}
public class WeatherDataTest{
	public static void main(String[] args){
		WeatherData weatherData = new WeatherData();
		CurrentConditionDisplay display = new CurrentConditionDisplay(weatherData);
		weatherData.measurementsChanged(11.0f, 1.2f, 11);
	}
}

java.util.Observable的黑暗面:

可能你也注意到了,Observable是一個類,而不是一個接口,而且它自己也沒有實現一個接口,因此在很多方面限制了它的使用與複用,不符合我們設計原則,針對接口編程而不是針對實現編程。

首先Observable是一個類,你必須設計一個類繼承它,如果某類想同時具有另一個超類的行爲,則會陷入兩難,因爲JAVAv不支持多繼承。再者,Observable中的setChanged()方法是protected的,這就意味着除非你繼承自Observable,否則你無法創建Observable實例並組合到你自己的對象裏來。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章