設計模式——觀察者模式:天氣推送的兩種實現

需求說明:

目前咱們須要實現一種天氣實時更新的程序(天氣推送),當氣象站數據更新後,天氣接口程序去獲取最新天氣數據,而後將數據分發給全部訂閱過「天氣日報」程序的用戶,即便更新數據。java

(案例來源於《Head First 設計模式》)設計模式

總體結構圖:

由上圖能夠看出,氣象站負責去檢測天氣狀況,當數據發生變化時,天氣服務獲取了變化的數據而且須要將數據分發給衆多訂閱「天氣推送」的用戶,若是咱們採用一個個去通知的形式去實現的話,那麼當咱們新增了一個觀察者,咱們又須要單獨重複編寫發佈信息代碼,當咱們想去掉一個觀察者的時候,又須要去刪減部分代碼,這樣的操做實在很繁瑣,耦合度也比較高。接下來咱們採用觀察者模式來實現這個需求,來看看有沒有什麼神奇的地方。測試

觀察者模式

觀察者模式定義了一系列對象之間的一對多關係,當一個對象(主題)改變狀態,其餘依賴者都會收到通知。this

觀察者模式組成

抽象主題角色:把全部對觀察者對象的引用保存在一個集合中,每一個抽象主題角色均可以有任意數量的觀察者。抽象主題提供一個接口,能夠增長和刪除觀察者角色。通常用一個抽象類和接口來實現。spa

抽象觀察者角色:爲全部具體的觀察者定義一個接口,在獲得主題的通知時更新本身。.net

具體主題角色:在具體主題內部狀態改變時,給全部登記過的觀察者發出通知。具體主題角色一般用一個子類實現。設計

具體觀察者角色:該角色實現抽象觀察者角色所要求的更新接口,以便使自己的狀態與主題的狀態相協調。一般用一個子類實現。若是須要,具體觀察者角色能夠保存一個指向具體主題角色的引用。code

觀察者模式原型

觀察者實現方式

1.主題主動推送數據

當數據更新後,主題會將全部更新數據都推送給觀察者,而觀察者只能被動接受主題的推送信息。server

2.觀察者主動拉取數據

當數據更新後,主題會通知觀察者數據更新了,並暴露出相關數據的getter方法,觀察者們可根據本身的須要去拉取本身須要的信息。對象

兩種方式的代碼實現

方式一:主題推送數據

這裏咱們採起本身編寫抽象主題與抽象觀察者代碼的方式來實現。

抽象主題:SubjectInterface.java

package observer.one.subject;

import observer.one.observers.ObserverInterface;

public interface SubjectInterface {
	public void registerObserver(ObserverInterface o);
	public void removeObserver(ObserverInterface o);
	public void notifyObservers();
}

具體主題:WeatherData.java

package observer.one.subject.impl;

import java.util.ArrayList;

import observer.one.observers.ObserverInterface;
import observer.one.subject.SubjectInterface;

public class WeatherData implements SubjectInterface{
	
	private ArrayList<ObserverInterface> observers;
	//溫度
	private String temperature;
	//溼度
	private String humidity;
	//氣壓
	private String pressure;
	
	public WeatherData() {
		observers=new ArrayList<ObserverInterface>();
	}
	/**
	 * 訂閱
	 */
	public void registerObserver(ObserverInterface o) {
		observers.add(o);
	}
	/**
	 * 取消訂閱
	 */
	public void removeObserver(ObserverInterface o) {
		if(observers.indexOf(o)>=0){
			observers.remove(o);
		}
	}
	/**
	 * 通知觀察者
	 */
	public void notifyObservers() {
		for(ObserverInterface o:observers){
			o.update(temperature, humidity, pressure);
		}
	}
	public void setDataChange() {
		notifyObservers();
	}
	/**
	 * 數據改變後,通知觀察者
	 * @param temperature
	 * @param humidity
	 * @param pressure
	 */
	public void setNewData(String temperature,String humidity,String pressure){
		this.temperature=temperature;
		this.humidity=humidity;
		this.pressure=pressure;
		setDataChange();
	}
}

抽象觀察者:ObserverInterface.java

package observer.one.observers;

public interface ObserverInterface {
	void update(String temperature,String humidity,String pressure);
}

實際上,咱們看到這裏就會發現有點問題,一旦咱們的數據參數發生變化,就須要去修改抽象觀察者方法,而具體觀察者也須要去修改相關方法的實現,耦合性較大。

具體觀察者:Observer1.java

package observer.one.observers.impl;

import java.util.Date;

import observer.one.observers.ObserverInterface;
import observer.one.subject.SubjectInterface;

public class Observer1 implements ObserverInterface{
	
	private SubjectInterface subject;
	
	//溫度
	private String temperature;
	//溼度
	private String humidity;
	//氣壓
	private String pressure;
	
	public Observer1(SubjectInterface subject) {
		this.subject = subject;
		subject.registerObserver(this);
	}
	
	public void register(){
		System.out.println("------觀察者1訂閱成功-------");
		subject.registerObserver(this);
	}
	
	public void cancelRegister(){
		System.out.println("------觀察者1取消訂閱了-------");
		subject.removeObserver(this);
	}
	
	public void update(String temperature, String humidity, String pressure) {
		this.humidity=humidity;
		this.pressure=pressure;
		this.temperature=temperature;
		display();
	}

	private void display() {
		System.out.println("觀察者1----數據更新了("+new Date()+")-溫度:"+temperature+"-溼度:"+humidity+"-氣壓:"+pressure);
	}
	
}

測試方法

這裏爲了方便演示,我寫了兩個具體觀察者(Observer1 ,Observer2 ),實際上在具體實現中只須要寫一個,實例化多個便可。

package observer.one;

import observer.one.observers.impl.Observer1;
import observer.one.observers.impl.Observer2;
import observer.one.subject.impl.WeatherData;

public class Test {
	
	public static void main(String[] args) {
		WeatherData weatherData=new WeatherData();
		//訂閱天氣日報
		Observer1 observer1=new Observer1(weatherData);
		//訂閱天氣日報
		Observer2 observer2=new Observer2(weatherData);
		
		weatherData.setNewData("10", "20", "30");
		observer1.cancelRegister();
		weatherData.setNewData("10.5", "14.23", "15.65");
		observer1.register();
		weatherData.setNewData("15.5", "18.23", "17");
	}
}

輸出結果:

方式二:觀察者主動拉取數據

這裏咱們採起JDK本身提供的抽象主題與抽象觀察者的方式來實現。

抽象主題:java.util.Observable

抽象觀察者:java.util.Observer

具體主題:WeatherData.java

package observer.two.subject;

import java.util.Observable;

public class WeatherData extends Observable{
	//溫度
	private String temperature;
	//溼度
	private String humidity;
	//氣壓
	private String pressure;
	/**
	 * 數據更新方法
	 * @param temperature
	 * @param humidity
	 * @param pressure
	 */
	public void setNewData(String temperature,String humidity,String pressure){
		this.temperature=temperature;
		this.humidity=humidity;
		this.pressure=pressure;
		setChanged();
		notifyObservers("數據更新了,快來獲取吧");
	}
	
	public String getTemperature() {
		return temperature;
	}
	public String getHumidity() {
		return humidity;
	}
	public String getPressure() {
		return pressure;
	}
}

由上面代碼能夠看出,具體主題主動暴露出屬性的getter方法,當數據更新時調用setChange()方法,通知觀察者此時有數據更新,你能夠來獲取了。

具體觀察者:Observer1.java

package observer.two.observers;

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

import observer.two.subject.WeatherData;

public class Observer1 implements Observer{
	
	private Observable observable;
	
	//溫度
	private String temperature;
	//溼度
	private String humidity;
	//氣壓
	private String pressure;
		
	public Observer1(Observable observable) {
		super();
		this.observable = observable;
		System.out.println("------觀察者1訂閱了-------");
		observable.addObserver(this);
	}
	
	public void cancelRegister(){
		System.out.println("------觀察者1取消訂閱了-------");
		observable.deleteObserver(this);
	}
	/**
	 * 數據更新通知
	 */
	public void update(Observable o, Object arg) {
		System.out.println("氣象臺說:"+arg+"");
		System.out.println("觀察者1:嗯,這就去");
		if(o instanceof WeatherData){
			WeatherData data=(WeatherData)o;
			this.humidity=data.getHumidity();
			this.pressure=data.getPressure();
			this.temperature=data.getTemperature();
		}
		display();
		System.out.println("數據提取完畢,並已展現");
	}
	/**
	 * 數據打印
	 */
	private void display() {
		System.out.println("觀察者1----數據更新了("+new Date()+")-溫度:"+temperature+"-溼度:"+humidity+"-氣壓:"+pressure);
	}

}

此時,具體觀察者調用update方法,主動拉取主題的最新數據,並顯示出來。

測試方法:

package observer.two;

import observer.two.observers.Observer1;
import observer.two.subject.WeatherData;

public class Test {
	public static void main(String[] args) {
		WeatherData weatherData=new WeatherData();
		/*觀察者1訂閱天氣日報*/
		Observer1 observer1=new Observer1(weatherData);
		Observer1 observer2=new Observer1(weatherData);
		weatherData.setNewData("10", "79", "18");
	}
}

輸出結果:

兩種實現方式對比

細心的同窗應該能夠發現,本身編寫抽象主題代碼時,咱們編寫的接口(interface),而JDK官方提供的觀察者模式中,抽象主題採用的是繼承的形式實現。歸根結底這兩種實現方式的對比,是主題推送數據和觀察者拉取數據的對比,以及實現接口和繼承父類的對比。

推送數據和拉取數據的對比:

推送數據的實現方式主題須要把全部的參數都推送給觀察者們,這裏就須要事先在update方法中指定全部的參數,一旦參數發生改變,不只要改變主題的方法,還須要改變觀察者接口方法。
而拉取數據的實現方式只須要改變主題參數,不須要改變觀察者接口方法,觀察者們依舊根據本身的須要去獲取數據。  

實現接口和繼承父類的對比:

因爲JAVA只支持單繼承,實現接口和繼承父類的優劣勢你們也應該都很清楚。

對比結果:若是能夠的話,咱們應該本身去實現本身的抽象主題,而不採用JDK官方提供的實現方式,畢竟繼承的形式有必定的弊端。在數據獲取方面,最好採用具體觀察者拉取的形式,這樣更有利於後期進行擴展。具體的代碼實現,我就不共享出來了,仔細看了這篇文章的人基本均可以本身去實現。

相關文章:《設計模式——策略模式:會員價格體系的簡單實現

相關文章
相關標籤/搜索