本文为阅读《Head First 设计模式》一书的摘要总结
观察者 模式定义了对象之间的 一对多 依赖,这样一来,当一个对象改变状态时,它的所有依赖都会受到通知并自动更新。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
主题唯一依赖的东西是一个实现Observer
接口的 对象列表。当有新类型的观察者出现是,不需要改变主题代码,我们只需要新类型实现Observer
接口,并注册为主题的观察者(加入对象列表)。当改变主题时(对自身状态操作的改变),只要它还实现Subject
接口,观察者也不需要改变。所以它们之间是松耦合的。
本示例的功能是气象站将温度、湿度和气压数据发送给不同的展示牌。
主题:
public interface Subject {
void registryObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObserver();
}
public class WeatherData implements Subject{
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registryObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObserver() {
for(Observer o : observers){
o.update(temperature,humidity,pressure);
}
}
public void measurementsChanged(){
notifyObserver();
}
//该方法用来模拟一次测量
public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
观察者:
public interface Observer {
void update(float temp, float humidity, float pressure);
}
public interface DisplayElement {
void display();
}
public class CurrentConditionsDisplay implements Observer, DisplayElement{
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registryObserver(this);
}
@Override
public void display() {
System.out.println("CurrentDisplay:" + temperature + "F degrees and " + humidity + "% humidity");
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
在这个示例中,主题直接将自己的状态(温度、湿度和气压)传入观察者,这种方式起始并不够好。若是以后主题有新的状态加入,那么主题和观察者代码都需要修改。
在java.util
包下含有基本的Observable
类 和Observer
接口。这跟我们之前定义的Subject
接口与Observer
接口十分相似。但是使用Java内置的Observable
类和Observer
接口,我们可以使用 推(push) 或 拉(pull) 的方式来传送数据。
Observable
类的方法:
Observer
接口定义的方法:
现在我们来重构上面的代码:
可观察者:
public class WeatherData extends Observable {
private List<Observer> observers;
private Data data;
public WeatherData() {
observers = new ArrayList<>();
data = new Data();
}
public void measurementsChanged(){
setChanged();
notifyObservers(data);//将数据推送给观察者
}
public void setMeasurements(float temperature,float humidity,float pressure){
data.setTemperature(temperature);
data.setHumidity(humidity);
data.setPressure(pressure);
measurementsChanged();
}
}
在调用notifyObservers
方法之前,我们调用了setChanged
方法,这是必要的,我们看一下源码中notifyObservers
和setChaged
方法的实现:
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
protected synchronized void setChanged() {
changed = true;
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)//只有changed为true时,才会通知观察者
return;
arrLocal = obs.toArray(); //obs是订阅了该主题的观察者列表
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
protected synchronized void clearChanged() {
changed = false;
}
...
}
有源码可值,只有changed
为真时,才会通知观察者。changed
标志是十分有用的,setChanged
方法可以让我们在更新观察者是,有跟多的弹性,我们可以更适当的通知观察者。比如我们制定一定的规则,只有当满足条件时,我们才调用setChanged
方法,进行有效的更新。
若是我们使用的是notifyObservers(Object arg)
方法,那么就采用了 推 数据的方式。
观察者:
public class CurrentConditionsDisplay implements java.util.Observer, DisplayElement{
private Observable weatherData;
public CurrentConditionsDisplay(Observable weatherData) {
this.weatherData = weatherData;
weatherData.addObserver(this);
}
@Override
public void display(Data data) {
System.out.println("CurrentDisplay:" + data.getTemperature() + "F degrees and " + data.getHumidity() + "% humidity");
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData){
display((Data)arg);
}
}
}
java.util.Observer
接口定义的update
接受两个参数:一个Observable
对象,供我们判断是哪个可观察者送出了通知;一个Object
对象,这对应于notifyObservers(Object arg)
中的arg
。
以上是推数据,我们再做一点改变,就可以实现观察者自己拉数据:
可观察者:
public class WeatherData extends Observable {
private List<Observer> observers;
private Data data;
public WeatherData() {
observers = new ArrayList<>();
data = new Data();
}
public Data getData() {
return data;
}
public void measurementsChanged(){
setChanged();
notifyObservers();
}
public void setMeasurements(float temperature,float humidity,float pressure){
data.setTemperature(temperature);
data.setHumidity(humidity);
data.setPressure(pressure);
measurementsChanged();
}
}
我们为可观察者的状态data
添加了访问器,并且在通知观察者时,调用notifyObservers()
方法,没有传送具体的数据给观察者。
观察者:
public class CurrentConditionsDisplay implements java.util.Observer, DisplayElement{
private Observable weatherData;
public CurrentConditionsDisplay(Observable weatherData) {
this.weatherData = weatherData;
weatherData.addObserver(this);
}
@Override
public void display(Data data) {
System.out.println("CurrentDisplay:" + data.getTemperature() + "F degrees and " + data.getHumidity() + "% humidity");
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData){
Data data = (Data) arg;
if (data == null){
WeatherData weatherData = (WeatherData)o;
data = weatherData.getData();//拉取数据
}
display(data);
}
}
}
观察者这边的update
方法中,判断arg
为空,那么就主动调用访问器去拉取数据。
使用Java内置的Observable
类带来的弊端:
Observable
是一个类,这违背了 针对接口编程,而非针对实现编程,这导致我们在使用它时,必须设计一个类继承它。如果某个类想同时具有Observable
和另一个超类的行为,就会陷入两难。这限制了Observalbe
的复用能力。另外,因为没有Observalbe
接口,我们也不能建立自己的实现,和Java内置的Observer
API搭配使用。再者Observable.setChanged
被修饰为protected
,所以我们将Observable
实例组合到我们自己的对象中来后,无法调用setChanged
方法来正常通知观察者。