Observer Pattern 觀察者模式


觀察者模式概述:

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

就像報紙和雜誌的訂閱:
1、報社的業務就是出版報紙,這相當於觀察者模式裏的主題,主題裏的狀態發生變化就相當於有了新聞,報社的任務是出 版報紙,而主題的任務就是發出變化的通知。


2、向某家報社訂閱報紙,只要它們有新報紙出版,就會給你送來。只要你是它們的用戶,你就會一直收到報紙。這相當於 觀察者模式裏的觀察者,一旦主題發生變化併發出通知,那麼所有依賴該主題(觀察了該主題)的觀察者都會收到通知。

3、當你不想再看報紙的時候,取消訂閱,就不會再收到他們的報紙。這就相當於某個觀察者取消觀察了該主題,那麼主題 再發生變化的時候發出變化的通知的時候該觀察者就不會再收到通知。

4、只要報社還在運營,就會有人一直向他們訂閱報紙或者取消訂閱報紙。


觀察者模式的結構:

觀察者模式裏的主題必須有所有的觀察者的引用,這樣纔可以發通知或者傳送數據給所有的觀察者。但是如果多出了一個觀察者就要在主題裏添加一個觀察者的引用,那麼就會一直去改原有的代碼,而且還會破壞代碼的封裝性。所以可以設計一個接口,讓所有的觀察者都去實現該接口,藉口裏有一個方法供主題去調用來通知所有的觀察者數據發生了變化。這樣主題只需持有這個觀察者接口的引用即可。

有些新類型的觀察者出現時,主題的代碼不需要修改。假如我們有個新的具體類需要當觀察者,我們不需要爲了兼容新類型而修改主題的代碼,所有需要做的就是在新類裏實現Observer接口,然後註冊成爲觀察者就哦了。主題不在乎別的,它只會發送通知給所有實現了Observer接口並且註冊了該主題的觀察者。


主題接口:

<span style="font-size:14px;">package cn.wang;

public interface Subject {
	void registObserver(Observer o);//註冊到主題
	void unregistObserver(Observer o);//取消註冊
	void notifyObservers();//通知所有觀察者數據發生了變化
}</span>

觀察者接口:

<span style="font-size:14px;">package cn.wang;

public interface Observer {
	void update(String temp,String humidity,String pressure);
}</span>


具體的主題,實現主題接口:

<span style="font-size:14px;">package cn.wang;

import java.util.ArrayList;
import java.util.List;

public class WeatherDataSubject implements Subject {

	private float temp;
	private float humidity;
	private float pressure;
	private List<Observer> observers;

	/**
	 * 主題的構造方法,我們在構造方法裏維護一個Observer觀察者集合
	 */
	public WeatherDataSubject() {
		observers = new ArrayList<Observer>();
	}

	@Override
	// 註冊觀察者
	public void registObserver(Observer o) {
		observers.add(o);
	}

	@Override
	// 取消註冊觀察者
	public void unregistObserver(Observer o) {
		if (observers.contains(o)) {
			observers.remove(o);
		}
	}

	@Override
	public void notifyObservers() {
		for (Observer o : observers) {
			//這裏將float的數據轉換成String
			o.update(String.valueOf(temp), String.valueOf(humidity), String.valueOf(pressure));
		}
	}

	/**
	 * 氣象臺在數據變化的時候會調用該方法,不用管它是怎麼被調用的,可能是傳感器檢測到溫度變化了就會調用它
	 */
	private void dataSetChanged() {
		// 數據發生了變化,就通知所有的觀察者
		notifyObservers();
	}

	public void setData(float temp, float humididy, float pressure) {
		this.temp = temp;
		this.humidity = humididy;
		this.pressure = pressure;
		// 數據發生變化調用dataSetChanged方法
		dataSetChanged();
	}

}</span>


溫度數據的觀察者:

<span style="font-size:14px;">package cn.wang;

public class TemperatureObserver implements Observer {

	private Subject subject;//這裏生成了主題的引用是爲了以後可以取消註冊。

	/**
	 * 觀察者實現了Observer接口之後要註冊到主題,在這裏我們在構造函數裏進行註冊
	 */
	public TemperatureObserver(Subject subject) {
		//這裏也可以註冊其他的主題,只要相應的主題實現了Subject接口即可
		this.subject = subject;
		subject.registObserver(this);
	}

	@Override
	public void update(String temp, String humidity, String pressure) {
		// 這種方式是推的形式,每個觀察者選擇自己需要的數據來進行操作
		System.out.println("當前溫度是: " + temp);
	}

}</span>


溼度數據的觀察者:

<span style="font-size:14px;">package cn.wang;

public class HumityObserver implements Observer {

	private Subject subject;

	public HumityObserver(Subject subject) {
		this.subject = subject;
		subject.registObserver(this);
	}

	@Override
	public void update(String temp, String humidity, String pressure) {
		// 這種方式是推的形式,每個觀察者選擇自己需要的數據來進行操作
		System.out.println("當前的溼度是: " + humidity);
	}

}</span>

氣壓數據的觀察者:

<span style="font-size:14px;">package cn.wang;

public class PressuerObserver implements Observer {

	private Subject subject;

	public PressuerObserver(Subject subject) {
		this.subject = subject;
		subject.registObserver(this);
	}

	@Override
	public void update(String temp, String humidity, String pressure) {
		// 這種方式是推的形式,每個觀察者選擇自己需要的數據來進行操作
		System.out.println("當前的氣壓是: " + pressure);
	}

}</span>

測試類:

<span style="font-size:14px;">package cn.wang;

public class Test {

	public static void main(String[] args) {
		// 實例化一個氣象站
		WeatherDataSubject s = new WeatherDataSubject();

		// 實例化幾個觀察者
		Observer tempObserver = new TemperatureObserver(s);
		Observer humidityObserver = new HumityObserver(s);
		Observer pressuerObserver = new PressuerObserver(s);
		
		//改變主題的數據
		s.setData(1.0f, 0.5f, 4.3f);
		
		s.setData(2.0f, 0.6f, 5.2f);
	}
}</span>

輸出結果是:

<span style="font-size:14px;">當前溫度是: 1.0
當前的溼度是: 0.5
當前的氣壓是: 4.3
當前溫度是: 2.0
當前的溼度是: 0.6
當前的氣壓是: 5.2</span>


觀察者模式松耦合:

我們可以獨立地複用主題和觀察者。如果我們需要在別的地方使用這些主題或者觀察者,可以輕易的複用,因爲這兩者並不是緊耦合。

改變主題或觀察者的其中一方,並不會影響另一方。因爲兩者是松耦合的,所以只要它們之間的接口仍被遵守,我們就可以自由滴改變它們。其實我們編碼爲了後期更好的可擴展和可維護性,應該爲了交互對象之間的松耦合設計而努力。松耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因爲對象之間的互相依賴降到了最低。


Java API裏的觀察者模式:

Java API裏已經封裝了裝飾者模式,在java.util包下有一個Observable抽象類,這就相當於我們的Subject主題接口;有一個Observer接口,這就相當於我們自己寫的觀察者需要實現的觀察者接口。

Observable和Observer接口用起來很方便,因爲許多功能都已經事先實現好了。你甚至可以用推或者拉的方式來獲取變化的數據。

java API裏觀察者的類圖:



java API裏的主題,Observable:

使用的時候我們要寫一個自己的主題來繼承Observable類,同時會繼承addObserver方法和deleteObserver方法,這兩個方法分別是註冊該主題和取消註冊該主題。還會繼承一個notifyObservers方法,這個方法會在數據發生變化的時候被調用,這個方法會調用所有觀察者的update方法去更新觀察者的數據。另外java API還爲我們提供了一個setChanged方法,這個方法主要是爲了更精確的來確定數據是否發生了變化,例如:如果溫度變化了0.01°,那麼主題就會通知所有的觀察者數據發生了變化,但是這種情況不是我們想要的,我們有可能需要在溫度變化了0.5°的時候再通知觀察者,這時就可以在通知所有觀察者之前進行判斷,如果溫度的變化確實超過了0.5°,那麼就先調用setChanged方法,確定數據確實發生了變化,這個時候纔會調用觀察者的update方法,否則如果條件不滿足0.5°,也就是沒有調用setChanged方法,那麼即使數據發生了變化也不會通知觀察者。

java API裏的觀察者,Observer接口:

觀察者就需要去實現Observer接口,同時會取實現update方法。這就跟我們自己寫的觀察者接口一樣了。


具體用法:

另外還有一個不一樣的地方就是java的API同時支持推數據和拉數據。所謂的推數據就是指主題的數據發生變化的時候主題會調用觀察者的updae方法,並把所有變化的數據一股腦推送給所有的觀察者,不管這些觀察者用不用得到這些數據,這種方式調用的方法是notifyObserver(Object args)方法,其中參數就是變化的數據。拉數據則是指主題調用觀察者的update方法的時候把自己的引用傳過去,這樣觀察者就可以在update方法里根據自己的業務去拉主題的數據,這種方式下主題需要有getter方法來獲取對象中的數據,這種方式調用的是notifyObservers()方法。具體的做法如下:

主題

<span style="font-size:14px;">package com.wang;

import java.util.Observable;

public class WeatherDataSubject extends Observable {
	private float temp;
	private float humidity;
	private float pressure;
	
	/**
	 * 氣象臺在數據變化的時候會調用該方法,不用管它是怎麼被調用的,可能是傳感器檢測到溫度變化了就會調用它
	 */
	private void dataSetChanged() {
		// 數據發生了變化,就通知所有的觀察者
		//在通知所有觀察者之前要先調用這個方法
		setChanged();
		notifyObservers();//如果不傳參,就是拉的形式
	}

	public void setData(float temp, float humididy, float pressure) {
		this.temp = temp;
		this.humidity = humididy;
		this.pressure = pressure;
		// 數據發生變化調用dataSetChanged方法
		dataSetChanged();
	}
	
	//給各個屬性設置一個getter方法,方便觀察者拉數據
	public float getTemp() {
		return temp;
	}
	public float getHumidity() {
		return humidity;
	}
	public float getPressure() {
		return pressure;
	}
}</span>

溫度觀察者:

<span style="font-size:14px;">package com.wang;

import java.util.Observable;
import java.util.Observer;

public class TemperatureObserver implements Observer {

	private Observable o;

	/**
	 * 註冊到Observable的具體類
	 * @param 被觀察者,Observable的子類
	 */
	public TemperatureObserver(Observable o) {
		this.o = o;
		o.addObserver(this);
	}

	@Override
	public void update(Observable o, Object arg) {
		if (o instanceof WeatherDataSubject) {
			// 拉數據
			System.out.println("當前的溫度是: " + ((WeatherDataSubject) o).getTemp());
		}
	}

}</span>

溼度觀察者:

<span style="font-size:14px;">package com.wang;

import java.util.Observable;
import java.util.Observer;

public class HumidityObserver implements Observer {

	private Observable o;

	public HumidityObserver(Observable o) {
		this.o = o;
		o.addObserver(this);
	}

	@Override
	public void update(Observable o, Object arg) {
		if(o instanceof WeatherDataSubject){
			System.out.println("當前的溼度是: "+((WeatherDataSubject)o).getHumidity());
		}
	}

}</span>


氣壓觀察者:

<span style="font-size:14px;">package com.wang;

import java.util.Observable;
import java.util.Observer;

public class PressureObserver implements Observer {

	private Observable o;

	public PressureObserver(Observable o) {
		this.o = o;
		o.addObserver(this);
	}

	@Override
	public void update(Observable o, Object arg) {
		if(o instanceof WeatherDataSubject){
			System.out.println("當前的氣壓是: "+((WeatherDataSubject)o).getPressure());
		}
	}

}</span>

測試類:


<span style="font-size:14px;">package com.wang;

import java.util.Observer;

public class Test {

	public static void main(String[] args) {
		// 實例化一個主題
		WeatherDataSubject subject = new WeatherDataSubject();

		// 實例化溫度觀察者
		Observer tempObserver = new TemperatureObserver(subject);

		// 實例化溼度觀察者
		Observer humidityObserver = new HumidityObserver(subject);

		// 實例化氣壓觀察者
		Observer pressureObserver = new PressureObserver(subject);
		
		//改變主題的數據
		subject.setData(1.0f, 0.5f, 2.3f);
		System.out.println("-----------------------------");
		subject.setData(1.2f, 0.8f, 3.2f);
	}
}
</span>

結果:

<span style="font-size:14px;">當前的氣壓是: 2.3
當前的溼度是: 0.5
當前的溫度是: 1.0
-----------------------------
當前的氣壓是: 3.2
當前的溼度是: 0.8
當前的溫度是: 1.2</span>



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