观察者模式
行为型模式
在观察者模式中主要有两大类对象:
一个是被观察的对象,叫做主题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是一个类而不是接口,所以只能继承,不太符合面向接口编程的原则。
注意事项
- 当存在链式触发时,要小心可能出现循环引用。
- 上面的接口也可以定义成类
- 观察者可以实现为在运行时自主注册,而不一定要在构造方法里注册。