最近在读《HeadFirst设计模式》,这本书口碑不错,通俗易懂读着不累,应该比GoF的那本友好一些。本篇博客(以后关于设计模式的博客也同样)是阅读此书时整理的笔记,感谢这本书的作者~
什么是观察者模式?
观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,所有依赖者都会收到通知并自动更新。
在观察者模式中,有一个主题对象,和多个观察者。主题对象一旦发生改变,就会通知所有观察者进行更新。观察者可以注销自己的观察者身份,那么便不再收到通知;非观察者也可以注册成为一个观察者。
举一个生活中的例子,观察者模式就类似于报社(主题对象)和订阅者(观察者),一旦一个人订阅了报社的报纸,就成了观察者。报社一旦有新报纸,就会发给所有订阅者。订阅者如果不想看这个报纸了,可以取消订阅,便不会再收到报纸。JDK中的监听器ActionListener和事件ActionEvent其实就是观察者模式的一个典型应用.
为什么需要观察者模式?
在一些业务场景下,需要实现类似报社-订阅者这样的功能,有多个对象需要依赖于一个对象,根据这个对象的变化进行更新。
在《Headfirst设计模式》这本书里提到的例子是:有一些测量仪器,负责测量湿度、温度之类的,一旦这些指标发生变化,多个公布板都要实时更新,这些面板包括:现状布告板、气象统计布告板、预测布告板。此外,后续会添加其他各种布告板。
现有的类是WeatherData类,该类如下图所示:
最简单粗暴的方式就是,在measurementsChanged()里面直接挨个 panel.update(temperature, humidity, pressure),有几个布告板就update几次。
这样的问题是,当删除了某个布告板,或新增了某个布告板时,需要去改动这里的代码,说白了是面向实现编程而不是面向接口编程,而且类和类之间会互相影响,违背了设计的原则。因此,观察者模式的必要性就体现出来啦!
观察者模式的实现
接下来,就以上面所说的气象站的功能为例,用观察者模式实现他吧!
首先,我们要定义主题对象与观察者,实现了Subject接口的类是主题对象,所有实现Observer接口的类都为观察者。
观察者接口:
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
主题对象接口:
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
布告板接口:
public interface DisplayElement {
public void display();
}
接下来实现WeatherData类:
public class WeatherData implements Subject{
private ArrayList<Observer> observers;
private float temprature;
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 notifyObservers(){
for (Observer o : observers){
o.update(temprature, humidity, pressure);
}
}
public void measurementsChanged(){
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure){
this.temprature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
可以看到,WeatherData类内部有一个Observer数组,保存所有观察者,注册和删除观察者的功能通过增加/移除数组中的元素实现,数据更新后会立马通知观察者,调用观察者的更新方法。
接着定义布告板对象(即需要实现Observer接口和DisplayElement接口的观察者对象),只写了一个现状布告板,其他的异曲同工,没必要写了:
public class CurrentConditionDisplay implements DisplayElement, Observer {
private float temprature;
private float humidity;
private float pressure;
private Subject weatherData;
public CurrentConditionDisplay(){
// pass
}
public CurrentConditionDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void setWeatherData(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void removeWeatherData(Subject weatherData){
weatherData.removeObserver(this);
this.weatherData = null;
}
public void update(float temperature, float humidity, float pressure){
this.temprature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display(){
System.out.println("temperature:" + temprature);
System.out.println("humidity:" + humidity);
System.out.println("pressure:" + pressure);
}
}
注意,在书中,直接将WeatherData对象作为这里构造函数的参数,构造时直接定义了对象所依赖的主题对象,我觉得不是很灵活,所以稍微做了一些改动,增添了setWeatherData()方法进行设置,而构造函数则增加了一个缺省的空构造函数。此外增加了一个删除Subject的方法removeWeatherData()。
运行程序进行测试:
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay display = new CurrentConditionDisplay();
display.setWeatherData(weatherData);
weatherData.setMeasurements(30, 65,34.5f);
weatherData.setMeasurements(20, 65,35.5f);
}
}
结果如下:
以上就是自己手动用观察者模式实现气象站功能的代码。事实上,Java内置了观察者模式,通过导入java.util包里的Observer接口和Observable类就可以实现,非常方便而且功能更多。
Java内置的观察者模式用法:
- 把对象变成观察者:实现java.util.Observer接口,调用Observable对象的addObserver()方法。
- 主题对象进行通知:继承java.util.Observable类,调用setChanged(), 调用notifyObservers()。setChanged方法把boolean变量changed由false改为true, notify方法会调用所有观察者的update方法,通知完毕时将changed改回false。setChanged
- 观察者接收通知:update(Observable o, Object args),args是notify的时候传的数据
接下来,用Java内置的观察者模式重写气象站:
首先是WeatherData类:
import java.util.Observable;
public class WeatherData extends Observable {
private float temprature;
private float humidity;
private float pressure;
public WeatherData(){ }
public void measurementsChanged(){
setChanged();
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure){
this.temprature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
public float getTemprature() {
return temprature;
}
}
以及CurrentConditionDisplay类:
import java.util.Observer;
import java.util.Observable;
public class CurrentConditionDisplay implements DisplayElement, Observer {
private float temprature;
private float humidity;
private float pressure;
private Observable weatherData;
public CurrentConditionDisplay(){
// pass
}
public CurrentConditionDisplay(Observable weatherData){
this.weatherData = weatherData;
weatherData.addObserver(this);
}
public void update(Observable o, Object arg){
if (o instanceof WeatherData){
WeatherData weatherData = (WeatherData)o;
this.temprature = weatherData.getTemprature();
this.humidity = weatherData.getHumidity();
this.pressure = weatherData.getPressure();
display();
}
}
public void display(){
System.out.println("temperature:" + temprature);
System.out.println("humidity:" + humidity);
System.out.println("pressure:" + pressure);
}
}
事实上,Java中的Observable类存在一些问题,主要表现在:
① 他是一个类,不是接口。如果一个类继承了一个父类,又想继承Observable类,是无法实现的。
② 如①所说,既然不能继承,那么尝试用组合的方式呢?也是不行的,因为Observable类中的setChanged方法是一个protected方法,只有子类能调用,所以组合也是不行的。