设计模式之观察者模式

从一个气象监测应用说起:

我们想做一个气象应用,有一个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实例并组合到你自己的对象里来。


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