設計模式之觀察者模式(一)

Dimple91發表於2019-03-27

前面兩篇已經帶大家走進了設計模式的世界,瞭解了策略模式,還有基本的OO基礎和OO原則,不知道你是否能讀懂以及瞭解呢。接下來,我們就要進入第二個模式的學習了,觀察者模式,讓我們來一窺究竟吧。

觀察者模式是JDK中使用最多的模式之一,可以幫你的物件知悉情況,不會錯過該物件感興趣的事。物件甚至在執行時可決定是否要繼續被通知。並且後續還會一併介紹一對多關係,以及鬆耦合。有了觀察者,訊息會變得更靈通。

還是老樣子,舉例說明吧。有一個氣象站,由WeatherData物件負責追蹤目前的天氣情況(溫度、溼度、氣壓)。現在需要建立一個應用,有三種佈告板,分別顯示目前的狀況、氣象統計及簡單的預報。當WeatherObject物件獲得最新的測量資料時,三種佈告板必須實時更新。重點就是實時更新,那我們就明白了,觀察者模式是最好不過了。具體的情況如下圖所示:

image

現在已經提供了WeatherData類,對比剛才的需求,來做進一步的分析。

image
我們知道些什麼?

  • WeatherData類具有getter方法,可以取得三個測量值:溫度、溼度和氣壓

  • 當新的測量資料備妥時,measurementsChanged()方法就會被呼叫

  • 我們需要實現三個使用天氣資料的佈告板:“目前狀況”佈告,“氣象統計”佈告,“天氣預報”佈告。一旦WeatherData有新的測量,這些佈告必須馬上更新

  • 此係統必須可擴充套件,讓其他開發人員建立定製的佈告板,使用者可以隨心所欲地新增或刪除任何佈告板。目前初始佈告板有三類:“目前狀況”佈告、“氣象統計”佈告、“天氣預報”佈告

如果按照上圖提供的框架,我們在measurementsChanged()方法內部會有多個update的程式碼,因為我們有三個佈告板,就會有三個冗餘程式碼呼叫update,而且根據針對具體實現程式設計,會導致我們以後再增加或刪除佈告板時必須修改程式來達到目的。所以我們需要用觀察者模式來進行優化這個流程。

public void measurementsChanged() {
    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure();
    
    // 這裡都是呼叫update方法
    currentConditionDisplay.update(temp,humidity,pressure);
    statisticsDisplay.update(temp,humidity,pressure);
    forecastDisplay.update(temp,humidity,pressure);
}
複製程式碼

認識觀察者模式

觀察者模式 = 出版者 + 訂閱者 就如同訂閱報紙一樣,你向某家報社訂閱了報紙,只要他們有報紙出版,就會給你送一份過來。當你不想看報,不再訂閱的時候,他們自然就不會繼續為你送報紙,這個是使用者主動的行為。主要報社還存在,你就可以訂閱/取消訂閱報紙,來達到觀察的效果。

所以,我們的觀察者也是這麼回事。我們在這裡稱之為主題(Subject)和觀察者(Observer)。當主體內資料改變,就會通知觀察者;觀察者訂閱主題,就能在主題資料更新時收到訊息。如果物件已經取消訂閱,那就失去了和主題的聯絡,獨立開來,收不到訊息。更形象點表達就用下圖表示:

image

在真實世界中,觀察者模式就會被定義成:

觀察者模式

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

主題和觀察者定義了一對多的關係。觀察者依賴於此主題,只要主題狀態一有變化,觀察者就會被通知。根據通知風格,觀察者可能因此新值而更新。

總結成類圖就是

image

這裡還涉及到一個知識點,就是鬆耦合的概念。當兩個物件之間鬆耦合,他們依然可以互動,但是不太清楚彼此的細節。觀察者模式提供了一種物件設計,讓主題和觀察者之間鬆耦合。這就是這節裡面提及的一個設計原則:為了互動物件之間的鬆耦合設計而努力 鬆耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因為物件之間的互相依賴降到了最低。

所以重點來啦,剛才的氣象站,你自己構想的設計圖是如何的呢。我引用了書本中的設計圖,快來看看你的,你們之間有何異同。

image

看到這裡,其實可能很多人也都知道,Java內建的JDK是有觀察者模式的支援的,我自己之前也用過,甚至說用的還行。但是這個我們放在下次講解。這次,我們先自己動手,結合上面提到的特點,以及上面的類圖來實現。提升下我們自己動手的能力。開始咯

實現氣象站

public interface Subject {
	// 註冊觀察者物件
	public void registerObserver(Observer o);
	// 刪除觀察者物件
	public void removeObserver(Observer o);
	// 當主題狀態改變時,這個方法會被呼叫,以通知所有的觀察者
	public void notifyObservers();
}

/**
 * 
 * @Title: update
 * @Description: 當氣象觀測值改變時候,主題會把這些狀態值當做方法的引數,傳遞給觀察者
 * @param temp  溫度
 * @param humidity  溼度
 * @param pressure 氣壓
 * @throws
 */
public void update(float temp, float humidity, float pressure);

/**
 * 
 * @Title:
 * @Description: DisplayElement 介面只包含一個方法。當佈告板需要顯示時,呼叫此方法
 * @Copyright:
 * @Company:
 * @author:XuYue
 * @version:Neon.3 Release (4.6.3)
 * @Create Date Time: 2019年3月26日 下午3:59:19
 * @Update Date Time: 2019年3月26日 下午3:59:19
 * @see
 */
public interface DisplayElement {
	public void display();
}
複製程式碼

在WeatherData中實現主題介面

public class WeatherData implements Subject {

	private ArrayList observers;
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() {
		observers = new ArrayList();
	}
	
	// 當註冊觀察者時,我們只要把它加到ArrayList中即可
	@Override
	public void registerObserver(Observer o) {
		observers.add(o);
	}

	// 當觀察者想取消註冊,我們把它從ArrayList中刪除
	@Override
	public void removeObserver(Observer o) {
		int i = observers.indexOf(o);
		if (i >= 0) {
			observers.remove(i);
		}
	}

	// 我們把狀態告訴每一個觀察者。因為觀察者都實現了update(),所以我們知道如何通知它們
	@Override
	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();
	}

}
複製程式碼

建立佈告板

/**
 * 
 * @Title:
 * @Description: 實現Observer介面,所以可以從WeatherData物件中獲得改變
 * 也實現了DisplayElement介面,因為我們的API規定所有佈告板都必須實現此介面
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {
	
	private float temperature;
	private float humidity;
	private Subject weatherData;
	
	public CurrentConditionDisplay(Subject weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}

	// 當update()被呼叫時,我們把溫度和溼度儲存起來,然後呼叫display()
	@Override
	public void update(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		display();
	}

	// 顯示溫度和溼度
	@Override
	public void display() {
		System.out.println("Current conditions: " + temperature + " F degrees and " + humidity + "% humidity");
	}
}
複製程式碼

啟動氣象站

public class WeatherStation {
	public static void main(String[] args) {
		// 建立有一個WeatherData物件
		WeatherData weatherData = new WeatherData();
		
		// 建立佈告板,把WeatherData物件傳給它們
		CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
		
		weatherData.setMeasurements(80, 65, 30.4f);
	}
}

// 結果
Current conditions: 80.0 F degrees and 65.0% humidity
複製程式碼

至此,我們根據一系列的流程,自己動手實現了氣象站的觀察者模式,是不是很酷,是不是覺得自己很棒,給自己鼓個掌吧。

觀察者模式的第一部分就先到這裡,我們從拿到題材,到理解觀察者模式,再到設計類圖,最後到功能實現,都是一步一個腳印,踏踏實實地在走。文中說的不明白的地方,可以繼續討論完善。這次學到了一個設計模式,一個設計原則,又一次鞏固了類圖的畫法,小夥伴們覺得有收穫嗎?

觀察者模式的下篇,會繼續完善這個模式,用Java自帶的觀察者進行實現,並對觀察者作出總結。我們下次再會。

PS:程式碼已經上傳,需要檢視的朋友點選此處HeadFirstDesign

愛生活,愛學習,愛感悟,愛挨踢

image

相關文章