在实现观察者模式前,先看一个天气预报系统的需求,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方法可以抽象成一个公共接口
……
要解决这些问题,这时候使用观察者模式就很方便了。
观察者定义了对象间的一对多依赖,当一个对象(主题)状态改变时,它的所有依赖者(观察者)都会收到通知并且自动更新,观察者移除后,不再收到通知。
观察者模式让主题和观察者之间松耦合。有新的观察者出现时,主题的代码不需要修改。观察者在主题中注册就行了,主题也不关心有多少观察者,只是发送通知给所有实现了观察者接口的对象。
观察者模式的简要类图:
其中,主题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();
}
一旦天气数据变化,调用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接口,这样灵活性下降。