設計模式-觀察者模式

氣象檢測應用

在這裏插入圖片描述
我們接到一個任務,現在有一個氣象站,氣象站採集的數據在一個WeatherData對象中,我們的任務是利用WeatherData對象獲取數據,並更新三個佈告板:目前情況、氣象統計和天氣預報

簡單的WeatherData對象

簡單的WeatherData對象應該是這個樣子的

class WeatherData{
	
	private float temperature; // 溫度
	private float humidity; // 溼度
	private float pressure; //氣壓 
	
	// 獲取溫度 溼度 氣壓
	public float getTemperature() {
		// 方法體
	};
	public float getHumidity() {
		// 方法體
	};
	public float getPressure() {
		// 方法體
	};
	
	// 數據發生變化時,獲取最新數據並更新佈告板
	public void measurementsChanged() {
		temperature = getTemperature();
		humidity = getHumidity();
		pressure = getPressure();
		// 三個佈告板分別更新
		Board1.update(temperature, humidity, pressure);
		Board2.update(temperature, humidity, pressure);
		Board3.update(temperature, humidity, pressure);
	}
}

可是我們看到:三個佈告板是分別更新的,而且參數固定,如果以後有新的佈告板需要添加、或者每個佈告板顯示的數據需要添加,代碼維護起來就會很麻煩

// 這一段
// 三個佈告板分別更新   參數(每個佈告板要顯示的數據)固定
Board1.update(temperature, humidity, pressure);
Board2.update(temperature, humidity, pressure);
Board3.update(temperature, humidity, pressure);

設計模式-策略模式中,我們提到過這樣的設計原則:找出應用中可能需要變化之處,把他們獨立出來,不要和那些不需要變化的代碼混在一起
根據這個原則,在WeatherData對象中,佈告板一定是變化的,所以要把它單獨拿出來

觀察者模式

在修改上面的代碼之前,我們先了解一下什麼是觀察者模式

對於報紙出版商和訂閱者之間

  • 只要你訂閱了報紙,你就是訂閱者
  • 出版商有了新的新聞就會給你以及其他所有訂閱者發送報紙
  • 你不用時時刻刻關注出版商有什麼新聞變化,因爲如果有新聞變化出版商會自動給你派發報紙
  • 如果你不想看報紙,就取消訂閱

這種形式就是觀察者模式,在觀察者模式中,出版商叫做“主題”(Subject),訂閱者叫做“觀察者”(Oberver)

在這裏插入圖片描述在這裏插入圖片描述觀察者無需時時刻刻監視主題的變化,主題發生變化時會自動通知觀察者

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

實現觀察者模式的方法不止一種,但是以包含SUbjectObserver接口的類設計的做法最常見
我們來看一下

設計氣象站

我們將氣象監測系統分爲兩類,第一類就是WeatherData對象所代表的Subject,第二類就是所有佈告板Board1,2,3……所代表的Observer

首先設計Subject接口,需要的功能:註冊和刪除觀察者,將新信息推送給所有觀察者

public interface Subject{
	// 註冊觀察者
	public void registerObserver(Observer o);
	// 移除觀察者
	public void removeObserver(Observer o);
	// 通知所有佈告板
	public void notifyObservers();
}

Observer接口只要實現更新就可以了

public interface Observer{
	// 數據發生變化時,更新自身的值更新自身的值
	public void update(float temperature, float humidity, float pressure);
}

現在我們的WeatherDate類實現Subject接口

class WeatherData implements Subject{
	private float temperature; // 溫度
	private float humidity; // 溼度
	private float pressure; //氣壓 
	private ArrayList observers; //記錄所有註冊WeatherDate的觀察者,在構造函數中初始化

	public WeatherData(){
		observers = new ArrayList();// 初始化ArrayList
	}
	
	//註冊新的觀察者
	public void registerObserver(Observer o){
		observers.add(o);
	}

	// 移除觀察者
	public void removeObserver(Observer o){
		// 查找觀察者是否已經訂閱,如果訂閱了返回位置,未訂閱返回-1
		int i = observers.indexOf(o);
		if( i>= 0)
			observers.remove(o);
	}
	
	// 通知每個觀察者更新自己
	public void notifyObservers() {
		for(int i=0; i<observers.size(); i++) {
			Observer 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();
	}
}

每個佈告板都實現Observer接口,我們就以Board2爲例


public class Board2 implements Observer{
	private float temperature; // 溫度
	private float humidity; // 溼度
	private float pressure; //氣壓 
	private Subject weatherData // 記錄訂閱的是哪個Subject
	
	public Board2 (Subject weatherData){
		// 訂閱WeatherData
		this.weatherData = weatherData;
		// 將自己添加到WeatherData的觀察者列表中
		weatherData.registerObserver(this);
	}
	
	public void update(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		//顯示數據
		display();
	}
	
	// 顯示數據
	public void display() {
		System.out.println(temperature + " "
				+ humidity + " "
				+ pressure);
	}
}
  • 目前來說,當值變化時(update())調用display()時很合理的,然而還有很多更好的方法設計顯示數據的方式,如MVC設計模式
  • Subject的引用其實沒必要保存,因爲構造完Board2之後就用不到了,但是以後我們可能想要取消註釋,如果已經有了對Subject的引用會比較方便

到目前爲止我們已經從無到有的完成了觀察者模式

使用Java內置的觀察者模式

在這裏插入圖片描述

JavaAPI有內置的觀察者模式,java.util包中包含最基本的Observer接口ObserverableObservable相當於Subject,注意一個是類,一個是接口

註冊觀察者

只要一個類實現了Observaer接口,那麼就可以調用任何Observable對象的addObserver()方法,把自身註冊到該Observalbe上,不想觀察時可以調用Observable對象的deleteObserver()方法

主題(Subject、Observable)發出通知

定義一個類擴展java.util.Observable類,這之後

  • 首先調用setChanged()方法,標記爲“當前主題的數據已經發生了變化”
  • 然後調用notifyObservers方法中的任意一個:notifyObservers(),或notifyObservers(Object arg),後者可以傳遞任何類型的數據給觀察者

爲什麼設置setChanged()

如果setChanged()沒有設置,那麼調用notifyObservers就不會通知觀察者,調用notifyObservers之後,又會回到沒有設置setChanged()的狀態

這樣的好處是可以自定義通知頻度,比如氣象觀察站的溫度都是0.01這樣變化的,而且變化極快,每次變化都會通知觀察者

可是佈告板上不需要這麼精細,只需要每1度變化就可以了,那麼我們就可以每當有整整1度的溫度發生變化時再設置setChanged()讓它通知觀察者
在這裏插入圖片描述

觀察者接收通知

觀察者有update(Observable o, Object arg)方法,第一個參數表示是哪個主題通知它的,第二個參數是主題調用其自身的notifyObserves時傳遞的參數
在這裏插入圖片描述

關於主題推送變化和觀察者拉取變化

主題的信息發生變化時,其實這個變化不是主題“推”給觀察者的,而是主題通知觀察者,讓觀察者主動"拉"下來的
Observable源碼大意是這樣的

nottifyObservers(Object arg){
	if(設置了setChanged){
		for( 觀察者列表中的每一個觀察者 ){
			update( this, arg );
		}
		將類設置爲 未設置setChanged 之前的狀態
	}
}

nottifyObservers(){
	nottifyObservers( null );
}

程序不要依賴於通知順序

Observable的數據發生變化時,Observer被通知到的順序是不一定的,所以你的程序千萬不要建立在某一個特定通知順序的上

java.util.Observable的黑暗面

java.util.Observable是個類!嘿你說氣不氣,甚至它一個接口都沒實現,也就是說,java.util.Observable的的實現會有很多問題,限制了它的使用和複用

  • 如果一個類相同時繼承超類和java.util.Observable就不可能了
  • 沒有Observable接口,也就說明了你不能創造一個實現Observable的類來和java內置的Observable API搭配使用
  • java.util.Observable類中,setChanged()方法是project的,這也就意味着:除非你的類繼承自Observable類,否則無法創造Observable實例組合到自己的對象中,這違背了我們在設計模式-策略模式提到過的設計原則:“多用組合,少用繼承”

觀察者模式遵循設計原則

  • 設計原則:找出程序中會變化的方面,然後將其和固定不變的方面分離
    – 觀察者模式中,會改變的是主題的狀態,以及觀察者的數目和類型。用這個模式,你可以改變依賴於主題狀態的對象,卻不必改變主題,這就叫提前規則

  • 設計原則:針對接口編程,不針對實現編程
    – 主題與觀察者都使用接口,觀察者利用主體的接口向主題註冊,主題利用觀察者接口通知觀察者,這樣可以讓兩者之間運作正常,又同時鬆耦合的優點

  • 設計原則:多用組合,少用繼承
    – 觀察者模式利用“組合”將許多觀察者組合進主題中。對象之間的這種關係不是通過繼承產生的,不是在運行時利用組合的方式產生的


參考:《Head First 設計模式》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章