10分钟一个设计模式系列
The Observer Pattern
1. 基础 Basic
Observer Pattern,说穿了,类似于手机应用里面的“发布-订阅”的形式,有一个Subject接口提供内容的获取,更新,并且Subject需要注册(register)很多个Observer观察者,而具有显示数据功能Display的类将继承Observer接口作为观察者,只有当你在Subject里注册了这个Observer时,Subject一有数据更新就会通知(notify)所有注册的Observers去更新(update)内容。
现在我们来看看一个UML图,摘自《Head First Design Patterns》。
在这里面,我们可以看到有Subject接口和Observer接口,分别有两个ConcreteXX的具体实现接口功能的类。这就是Observer模式的基本概念。
2. 例子 Example
我们来看一个具体的例子,有一个气象台WeatherStation负责获取天气的数据,天气气象台的类有3个getter,分别获取气温,湿度,气压,getTemperature(),getHumidity(),getPressure()。有很多个设备负责显示这些数据,那么应该如何实现这个例子?
Subject Interface:
1 public interface Subject { 2 public void registerObserver(Observer observer); 3 public void removeObserver(Observer observer); 4 public void notifyObservers(); 5 }
Observer Interface:
1 public interface Observer { 2 public void update(float temp, float humidity, float pressure); 3 }
WeatherData Class:
1 import java.util.Vector; 2 3 public class WeatherData implements Subject{ 4 private float temperature; 5 private float humidity; 6 private float pressure; 7 private Vector observers; 8 public WeatherData() { 9 10 } 11 12 @Override 13 public void registerObserver(Observer observer) { 14 // TODO Auto-generated method stub 15 observers.add(observer); 16 } 17 18 @Override 19 public void removeObserver(Observer observer) { 20 // TODO Auto-generated method stub 21 int index = observers.indexOf(observer); 22 if (index >= 0) 23 observers.remove(index); 24 } 25 26 @Override 27 public void notifyObservers() { 28 // TODO Auto-generated method stub 29 for (int i = 0; i < observers.size(); i++) { 30 Observer observer = (Observer)observers.get(i); 31 observer.update(temperature, humidity, pressure); 32 } 33 } 34 35 public void measurementChanged() { 36 notifyObservers(); 37 } 38 39 public void setMeasurement(float temperature, float humidity, float pressure) { 40 this.temperature = temperature; 41 this.humidity = humidity; 42 this.pressure = pressure; 43 measurementChanged(); 44 } 45 }
DisplayElement Interface(将动作抽象与实现分离):
1 public interface DisplayElement { 2 public void display(); 3 }
ConditionDisplay Class:
1 public class ConditionDisplay implements Observer,DisplayElement{ 2 private float temperature; 3 private float humidity; 4 private float pressure; 5 private Subject weatherData; 6 7 public ConditionDisplay(Subject weatherData) { 8 this.weatherData = weatherData; 9 } 10 11 @Override 12 public void display() { 13 // TODO Auto-generated method stub 14 System.out.println("temperature: "+this.temperature); 15 System.out.println("humidity: "+this.humidity); 16 System.out.println("pressure: "+this.pressure); 17 } 18 19 @Override 20 public void update(float temp, float humidity, float pressure) { 21 // TODO Auto-generated method stub 22 this.temperature = temp; 23 this.humidity = humidity; 24 this.pressure = pressure; 25 display(); 26 } 27 28 }
3. Java内置API实现 Java Built in Support API - Observable Class & Observer Interface
其实,Java也有内置帮助实现Observer模式的功能,是java.util包里面的Observable类以及Observer接口。请注意,Observable是一个类,而Observer是一个接口,这两点很重要,稍后会详细解释。我们先看看Observable类。
其实,它就是一个类似的Subject接口的类,但这里面有一个setChanged()方法。当你的Subject数据更新时,你需要先setChanged(),再notifyObservers(),为什么会多出来这个setChanged()方法?暂且不表,我们先看一下notifyObservers()在java里是如何实现的。
1 public void notifyObservers(Object arg) { 2 Object[] arrLocal; 3 synchronized (this) { 4 if (!changed) 5 return; 6 arrLocal = obs.toArray(); 7 clearChanged(); 8 } 9 for (int i = arrLocal.length-1; i>=0; i--) 10 ((Observer)arrLocal[i]).update(this, arg); 11 }
从这段代码可以看到,它会有一个先判定boolean changed是否改变的过程。然后再一一调用Observer数组中的Observer.update()去更新各个Observer的内容。
那刚才的问题就可以解答了,为什么会多出来这个setChanged()方法?这个方法的好处是,你的subject不一定每时每刻都要去告诉observer观察者去更新内容,只有当想通知时,再通知。这就是好处了,但值得注意的是,如果去看java的源代码,setChanged()是protected,这也就会引起直接用java提供的类和接口实现这个模式会出现的一些不方便的地方。
Java中的Observer是一个接口,也是一个update()方法,所以没什么好说的。
4. Java Observable类的限制 Limitation of the Java Observable Class
刚才说过,java中Observable是一个类,而且类中的setChanged()是一个protected方法。这就带来了一个问题,因为它是一个类,那么你只能作为一个子类subclass去继承它,但假如我们这个类需要继承别的具体的类呢,或许有人会说new一个Observable实例出来,但这里又有一个问题,它是setChanged()是protected方法,那么new出来的这个实例是无法调用setChanged()方法的,无法调用它,就没办法notifyObservers()。这里提供两种方法来解决这个问题:
1. 照旧继承你原来继承的另外的类,然后直接在这个子类里面重新实现Observable类的内容,或者重新写一个Subject接口,添加Observable的方法。
2. 继承Observable,然后在subclass里面创建一个你原本想继承的那个类的实例作为一个“中介”,去实现具体的其它功能。