观察者模式
行为型模式
在观察者模式中主要有两大类对象:
一个是被观察的对象,叫做主题Subject
,Subject
具有数据,需要更新并提供给另一大类对象。
这另一大类对象就是观察者Observer
,Observer
不断获取Subject
的数据更新,而且主要是处于一种被动的状态,也就是等待Subject
主动推送push
,少数时候需要自己向Subject
拉取pull
。
观察者和被观察者之间是松耦合
的,因为被观察者并不需要知道具体是谁正在观察它,它只需要将观察者保存在列表里,然后统一向列表里的每个观察者进行推送即可。如果是不需要推送了的观察者,就直接从列表里删掉,新增观察者就添加到列表里。
一个简单的例子
已知的类和接口
WeatherData
类有三个gettter
方法可以用来获取湿度、温度和气压:getTemperature
,getHumidity
,getPressure
,三个方法的具体实现未知。当数据发生变化时,应调用measurementsChanged
方法。- 三个用来显示天气数据的布告板类:
CurrentConditionsDisplay
,StatisticsDisplay
,ForecastDisplay
。它们都有一个update
方法来接收数据更新。
需求
- 三个布告板都能及时收到数据更新,也就是要在
measurementsChanged
方法中调用这三个布告板的update
方法。 - 以后可能会加入新种类的布告板或者去掉旧的布告板,所以实现中要考虑可扩展性。
最直观的实现
按照需求1,最直观的实现方法自然是先在measurementsChanged
中获取数据,然后直接对每个布告板调用update
。
1 2 3 4 5 6 7 8 9 10 11 12
| private CurrentConditionsDisplay currentConditionsDisplay; private StatisticsDisplay statisticsDisplay; private ForecastDisplay forecastDisplay;
public void measurementsChanged(){ float temp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); currentConditionsDisplay.update(temp, humidity, pressure); statisticsDisplay.update(temp, humidity, pressure); forecastDisplay.update(temp, humidity, pressure); }
|
显然,这样的做法是紧耦合
的。因为不仅需要在WeatherData
类中分别保存每个布告板的引用,而且在数据更新的时候还要分别对每个布告板调用update
。
现在只有三个布告板,代码量比较少,但是如果布告板比较多,代码量就会不断增加。而且还不符合需求2,可扩展性很差(只能通过修改源代码来进行增删)。
使用观察者模式
首先需要实现三个接口:Subject
, Observer
, DisplayElement
,分别对应了被观察者、观察者和布告板。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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
实现为被观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import java.util.ArrayList;
public class WeatherData implements Subject{ private float temperature; private float humidity; private float pressure; private ArrayList<Observer> observers; public WeatherData() { observers = new ArrayList<Observer>(); }
public void registerObserver(Observer o) { observers.add(o); }
public void removeObserver(Observer o) { observers.remove(o); }
public void notifyObservers() { for (Observer o : observers) { o.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(); } }
|
将布告板实现为观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 update(float temperature, float humidity, float pressure){ this.temperature = temperature; this.humidity = humidity; display(); }
public void display() { System.out.println("Current condition: " + temperature + "F degrees, humidity: " + humidity + "%"); } }
|
Java内置的支持
在java.util
中包含了Observable
类和Observer
接口,分别对应被观察者和观察者。与上面自行实现的不同之处在于:
Observable
调用setChanged
之后调用notifyObservers
才会真的去通知观察者。Observer
的update
方法接收第一个参数是Observable
,方便判断是哪个被观察者更新。Observable
是一个类而不是接口,所以只能继承,不太符合面向接口编程的原则。
注意事项
- 当存在链式触发时,要小心可能出现循环引用。
- 上面的接口也可以定义成类
- 观察者可以实现为在运行时自主注册,而不一定要在构造方法里注册。