您的位置:首页 > 其它

HeadFirst design pattern笔记-观察者模式

2013-06-30 22:04 381 查看
在实现观察者模式前,先看一个天气预报系统的需求,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接口,这样灵活性下降。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐