1 模式说明
观察者模式就像报纸出版商和订阅者一样,在观察者模式中出版者叫做“主题”(subject),订阅者叫做“观察者”(observer)。主题内的数据产生变化时,就会通知已经订阅该主题的观察者,观察者如果订阅了主题,一旦主题发生变化发来通知,观察者就会做出相应的调整。观察者的数量可以是很多,换句话说就是很多人可以同时订阅一个主题。主题和观察者是1对多的关系。
说明
(1)如图1,一共有5个对象,分别为:Subject,Cat,Dog,Mouse,Duck。其中Subject为主题对象,代表食堂,Cat,Dog,Mouse全部订阅了Subject对象:食堂,并且同为观察者。Duck没有订阅Subject:食堂,所以当食堂发出消息:2点吃饭,Duck是接收不到的。
(2)如图2,Duck由于没有订阅食堂的通知,总会错过吃饭的点,饿得受不了,于是决定也注册成为观察者。于是向食堂发送注册请求。
如图3,食堂接受到了Duck的注册请求,于是Duck成为了观察者,食堂又发送了开饭通知:8点吃饭。此时Cat,Dog,Mouse,Duck都收到通知8点吃放,于是Duck准时吃到了饭。
(3)如图4,Mouse觉得食堂的饭不好吃,决定换一家(或许他发现Cat也在吃饭,就赶紧跑了),自己向食堂提出请求,不在这吃了,也就是取消订阅。
如图5,食堂再一次通知:12点开饭。这时就只有Duck,Cat,Dog三人得到消息。Mouse就不知道到了。
(4)观察者类图
如图6:Subject
接口中,regitsterObserver()
方法是注册观察者,removeObserver()
方法是移除列表内的观察者,notifyObserver()
方法是通知观察者
Observer
接口是观察接口,updata()
方法是更新主题变换的方法。
2 引例(气象监测应用)
2.1 需求
如图7,左侧4个设备,都由weather-O-Rama 提供,其中温度计,湿度计,气压计分别测试温度,湿度和气压,气象站负责收集数据,并提供接口对外提供数据。右侧的WeatherData
数据对象负责从气象站获取最新的天气数据,同时向三块显示器提供数据,分别是:目前状况显示器,气象统计显示器,天气预报显示器。
Weather-O-Rama将他们设计的WeatherData源文件发送过来,如图8:三个getter方法分别获得当前以获取的测量值,measurementsChange()
方法是测量值更新,就调用此方法。
2.2 分析
(1)WeatherData类具有三个getter方法,可以获取三个测量值:温度,湿度,气压
(2)当新的测量值准备好后,measurement()
方法就会调用。
(3)需要实现是三个布告板:目前状况,气相统计,天气预告。一旦WeatherData有更新,布告板也要更新。
(4)需要考虑到可扩展性,未来不止这三个布告板,让其他开发人员可以自行定制布告板。
2.3 错误示例
我们可以看到,这个方法中的最后三行update()
方法违法设计原则,改变的地方应该封装起来。至少应该是一个统一的接口。
public class WeatherData(){
//实例变量声明
public void measurement(){
float temperature=getTemperature();
float humidity=getHumidity();
float pressure=getPressure();
currentConditionsDisplay.update(temperature,humidity,pressure);
statisticsDisplay.update(temperature,humidity,pressure);
forecastDisplay.update(temperature,humidity,pressure);
}
}
2.4 设计气象站
如图9:WeatherData
类实现Subject
接口,实现接口的三个方法。
三个布告板都分别实现Observer
接口和DisplayElement
接口。如果开发人员需要定制布告板,只需要实现Observer
接口和DisplayElement
接口。
2.5 具体代码
(1)主题接口和WeatherData
类
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObserver();
}
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList();
}
public void registerObserver(Observer o) {
observers.add(o);
}
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
public void notifyObserver() {
for(int i=0;i<observers.size();i++){
Observer observer= (Observer) observers.get(i);
observer.update(temperature,humidity,pressure);
}
}
public void measurementChanged(){
notifyObserver();
}
public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
measurementChanged();
}
}
(2)观察者接口
public interface Observer {
void update (float temp,float humidity,float pressure);
}
(3)布告板接口和一个实现类,剩下两个自行学习解决(其实是懒了)
public interface Observer {
void update (float temp,float humidity,float pressure);
}
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void display() {
System.out.println("Current Conditions:" + temperature + "F disgree and " + humidity + "% humidity");
}
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
(4)测试程序:气象站
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(25.6f, 30.5f, 33.5f);
}
}
3 java 内置观察者模式
Java中内置了观察者模式,是在java.util
包中的Observer
和Observable
类,java官方将主题作为类出现而不是接口。我们用java内置的观察者模式重新设计。
3.1类图
如图10,WeatherData
类由于是继承了Observeable
超类,所以自身不用实现registerObserver()
,removeObserver()
和notifyObserver()
三个方法。
可以看到Observeable
类中还有一个setChange()
方法,这是一个标记更新状态作用,让notifyObserver()
知道该不该通知观察者。
3.2内置观察者源码实现
看一下源码实现:可以看到,使用changed
成员变量控制是否通知观察者,changed
的初始值是false
,如果是false
,notifyObservers()
方法直接返回,不会通知观察者,如果是true
,notifyObservers()
方法才会起作用。
private boolean changed = false;
protected synchronized void setChanged() {
this.changed = true;
}
protected synchronized void clearChanged() {
this.changed = false;
}
public void notifyObservers() {
this.notifyObservers((Object)null);
}
public void notifyObservers(Object var1) {
Object[] var2;
synchronized(this) {
if (!this.changed) {
return;
}
var2 = this.obs.toArray();
this.clearChanged();
}
for(int var3 = var2.length - 1; var3 >= 0; --var3) {
((Observer)var2[var3]).update(this, var1);
}
}
3.3 内置观察者使用方法
(1) 如何把对象变为观察者
实现观察者接口Observer
,然后调用任何Observeable
对象的addObserver()
方法,如果不想在当观察者了,调用deleteObserver()
方法。
(2)可观察者(主题)如何发送通知。
第一步:调用Observeable
对象的setChanged()
方法。标记状态已经改变。
第二步:调用Observeable
对象的两个notifyObservers()
方法其中一个。
(3)观察者如何接受通知
和之前的一样,只是方法参数不同,第一个参数是标记是哪个主题,第二个参数是可观察者发送来的数据对象。
update(Observable var1, Object var2)
3.4 内置观察者代码实现
(1)WeatherData
类。
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
public void measurementChanged() {
setChanged();
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementChanged();
}
}
(2)CurrentConditionsDisplay
类和 DisplayElement
接口
public interface DisplayElement {
void display();
}
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
Observable observable;
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
public void display() {
System.out.println("Current Conditions:" + temperature + "F disgree and " + humidity + "% humidity");
}
public void update(Observable observable, Object o) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData) observable;
this.humidity = weatherData.getHumidity();
this.temperature = weatherData.getTemperature();
display();
}
}
}
(3)WeatherStation
类,测试代码
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(25.6f, 30.5f, 33.5f);
}
}