HeadFirst design pattern笔记-观察者模式

在实现观察者模式前,先看一个天气预报系统的需求,WeatherData对象负责获取天气数据(温度,湿度,压力),然后显示在公告板上,公告板可以有很多种类型。

不使用设计模式的做法:

	public class WeatherData {
		
		//气象测量更新数据时,此方法调用
		public void measurementsChanged() {
			//当前温度
			float temp = getTemperature();
			//当前湿度
			float humidity = getHumidity();
			//当前压力
			float pressure = getPressure();
			
			//更新公告板1
			currentConditionsDisplay.update(temp, humidity, pressure);
			//公告板2
			currentConditionsDisplay2.update(temp, humidity, pressure);
			//公告板3
			currentConditionsDisplay3.update(temp, humidity, pressure);
			.......
		}
	}
这种方法的缺点是显而易见的:

针对具体实现编程而不是接口,每个公告板都要加入到WeatherData类中,如果有公告板的变动,需要更改代码。

无法在运行时动态地增加/修改公告板

破坏了WeatherData的封装

公告板的update方法可以抽象成一个公共接口

……


要解决这些问题,这时候使用观察者模式就很方便了。


观察者定义了对象间的一对多依赖,当一个对象(主题)状态改变时,它的所有依赖者(观察者)都会收到通知并且自动更新,观察者移除后,不再收到通知。

松耦合:2个对象可以交互,但是不太清楚对方的细节,使得对象之间的互相依赖降到了最低

观察者模式让主题和观察者之间松耦合。有新的观察者出现时,主题的代码不需要修改。观察者在主题中注册就行了,主题也不关心有多少观察者,只是发送通知给所有实现了观察者接口的对象。

观察者模式的简要类图:


其中,主题Subjcet和观察者Observer是1对多的关系

Subject接口的3个操作,registerObserver用来注册观察者对象,removeObserver用来移除观察者对象,notifyObservers用来通知所有的观察者。

Observer接口的update方法用于接收通知,其实就是用于被Subject实现类的notifyObservers方法调用

现在看一下观察者模式下天气预报系统的实现:

WeatherData对象需要在数据变化时通知所有的公告板,根据上面的概念,WeatherData要实现Subjcet接口,公告板要实现Observer接口。

Subjcet接口:

public interface Subject {
	public void registerObserver(Observer o);
	public void removeObserver(Observer o);
	public void notifyObservers();
}

Observer接口:

public interface Observer {
	public void update(float temp, float humidity, float pressure);
}

WeatherData:

import java.util.ArrayList;

/**
 * 获取气象数据,通知观察者
 * */
public class WeatherData implements Subject {
	
	private ArrayList<Observer> observers;
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() {
		observers = new ArrayList<Observer>();
	}

	@Override
	public void registerObserver(Observer o) {
		observers.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		int i = observers.indexOf(o);
		if (i > 0)
			observers.remove(i);
	}

	@Override
	public void notifyObservers() {
		for (int i = 0; i < observers.size(); i++) {
			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();
	}
}

其中一种公告板CurrentConditionsDisplay:

/**
 * 公告板,其中一个观察者
 * */
public class CurrentConditionsDisplay implements Observer, DisplayElement {

	private float temperature;
	private float humidity;
	private float pressure;
	//主题的引用
	private Subject weatherData;
	
	public CurrentConditionsDisplay(WeatherData weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}
	
	@Override
	public void display() {
		System.out.println("temperature="+temperature+
				";humidity="+humidity+";pressure="+pressure);
	}

	@Override
	public void update(float temp, float humidity, float pressure) {
		this.temperature = temp;
		this.humidity = humidity;
		this.pressure = pressure;
		display();
	}
	
	
	//移除观察者
	private void quit() {
		weatherData.removeObserver(this);
	}
}

Displayment接口无关紧要,用于显示数据而已

public interface DisplayElement {
	public void display();
}


可以看到,实现Subject接口的WeatherData类中维护了一个Observer接口的Arraylist,以便动态维护注册和取消的观察者。

一旦天气数据变化,调用measurementsChanged方法,measurementsChanged方法调用notifyObservers,notifyObservers其实就是遍历在WeatherData类中注册的所有Observer(公告板),然后调用它们的update方法。这是一个“推”的过程,Observer们不需要手动去Subject那里取数据。这就实现了“当一个对象(主题)状态改变时,它的所有依赖者(观察者)都会收到通知并且自动更新,观察者移除后,不再收到通知。”的概念

现在调用测试代码:

public class WeatherStation {
	public static void main(String[] args) {
		WeatherData wData = new WeatherData();
		CurrentConditionsDisplay curDis = 
				new CurrentConditionsDisplay(wData);
		wData.setMeasurements(80, 65, 30.4f);
		wData.setMeasurements(81, 61, 33.4f);
		wData.setMeasurements(82, 62, 31.4f);
	}
}
控制台输出:

temperature=80.0;humidity=65.0;pressure=30.4
temperature=81.0;humidity=61.0;pressure=33.4
temperature=82.0;humidity=62.0;pressure=31.4


JAVA内置的观察者模式

JAVA中Observable类和Observer接口已经帮我们实现好了观察者模式,而且可以选择用“推”或者“拉”的方式。

推和拉的区别是观察者是否主动去主题那边取数据,这在代码中是通过notifyObservers方法是否传参体现的,如果传参则是推,不传参,更新的数据需要观察者调用主题的get方法得到,这样就是拉了。

改动以上的代码,注释部分显示了改动的地方,特别要注意的是和之前的程序对比,它们实现的接口的变化!

WeatcherData.java

import java.util.HashMap;
import java.util.Map;
import java.util.Observable;

/**
 * 获取气象数据,通知观察者
 * */
public class WeatherData extends Observable {//implements Subject {
	
//	private ArrayList<Observer> observers;
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() {
//		observers = new ArrayList<Observer>();
	}



//	@Override
//	public void registerObserver(Observer o) {
//		observers.add(o);
//	}
//
//	@Override
//	public void removeObserver(Observer o) {
//		int i = observers.indexOf(o);
//		if (i > 0)
//			observers.remove(i);
//	}
//
//	@Override
//	public void notifyObservers() {
//		for (int i = 0; i < observers.size(); i++) {
//			Observer observer = observers.get(i);
//			observer.update(temperature, humidity, pressure);
//		}
//	}
	
//	public void measurementsChanged() {
//		notifyObservers();
//	}
	public void measurementsChanged() {
		/*notifyObservers是Observable自带的方法,
		使用之前要调用setchanged,观察者才能收到通知
		*/
		setChanged();
		//现在把温度等信息封装在map中传给观察者
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("temperature", temperature);
		map.put("humidity", humidity);
		map.put("pressure", pressure);
		
		notifyObservers(map);
	}
	
	/**
	 * 模拟气象数据改变
	 * */
	public void setMeasurements(float temperature, float humidity,
			float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementsChanged();
	}
}

CurrentConditionsDisplay.java

import java.util.Map;
import java.util.Observable;

/**
 * 公告板,其中一个观察者
 * */
public class CurrentConditionsDisplay implements DisplayElement, java.util.Observer{//Observer, DisplayElement {

	private float temperature;
	private float humidity;
	private float pressure;
	//主题的引用
	//private Subject weatherData;
	//Observable对象的引用
	private Observable observerable;
	
//	public CurrentConditionsDisplay(WeatherData weatherData) {
//		this.weatherData = weatherData;
//		weatherData.registerObserver(this);
//	}
	public CurrentConditionsDisplay(Observable observable) {
		this.observerable = observable;
		observerable.addObserver(this);
	}

	@Override
	public void display() {
		System.out.println("temperature="+temperature+
				";humidity="+humidity+";pressure="+pressure);
	}

//	@Override
//	public void update(float temp, float humidity, float pressure) {
//		this.temperature = temp;
//		this.humidity = humidity;
//		this.pressure = pressure;
//		display();
//	}
	
	//使用java.util.Observer的update方法
	@Override
	public void update(Observable o, Object arg) {
		//检测是否是WeatherData这个主题传递的信息
<span style="white-space:pre">		</span>if(o instanceof WeatherData) {
<span style="white-space:pre">			</span>//这是拉的方式
<span style="white-space:pre">			</span>//WeatherData weatherData = (WeatherData)o;
<span style="white-space:pre">			</span>//this.temperature = weatherData.getXXXX
<span style="white-space:pre">			</span>
<span style="white-space:pre">			</span>//这是推的方式
<span style="white-space:pre">			</span>Map<String, Object> map = (Map<String, Object>)arg;
<span style="white-space:pre">			</span>this.temperature = (float) map.get("temperature");
<span style="white-space:pre">			</span>this.humidity = (float) map.get("humidity");
<span style="white-space:pre">			</span>this.pressure = (float) map.get("pressure");
<span style="white-space:pre">			</span>display();
<span style="white-space:pre">		</span>}
	}
}
测试类不变,运行结果:

temperature=80.0;humidity=65.0;pressure=30.4
temperature=81.0;humidity=61.0;pressure=33.4
temperature=82.0;humidity=62.0;pressure=31.4

用JAVA自带的实现可以方便很多,但是也有缺点,比如Observable是一个类而不是像第一个程序那样实现的Subjcet接口,这样灵活性下降。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值