在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新
代码实现
观察者模式应该是Java
和Android
中用到的非常多的一个设计模式了,Java
还提供了Observer
相关的实现类,它们在java.util.*
下,但它的Observable
是个实现类,我们使用时需要继承它,这样并不方便我们的使用, 今天我们就参照书籍和相关类自己实现一个传统的观察者模式
。
上一次我们公司通过策略模式(StrategyPattern)来实现了一个暂时完美的产品“Bird”。公司已经步入了正轨,名气也逐渐大了起来,气象站的老板Just找到我们想让我们帮它做一款气象站的应用。
应用的功能是三块布告板,一块用来显示当天的temperature(温度)、humidity(湿度)与pressure(气压),另一块用来显示天气预报,最后一块用来显示当前温度的酷热指数(就是天气爽不爽)。当然这只是目前的功能,以后可能会随时增加或移除公告板。
设计程序
观察者模式有两个主要的角色 :可观察的对象observable
或者也叫做Subject
,订阅者observer
。当observable
发生改变的时候(这里对应的就是天气发生了变化),它会通知所有的observer
(布告板),让它们根据情况去做自己的事情(更新显示的数据)。
气象站的功能和观察者模式非常吻合,小明决定直接参照Java
中的Observer
来实现:
记得上一次提起的设计原则——针对接口编程,而不是针对实现编程.我们将observer
和observable
的功能都用接口来声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface Observable {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObserver(Object arg); }
public interface Observer { void update(Observable observable,Object arg); }
|
这样我们就将observable
的注册、注销、和推送与Observer
的更新都给抽离出来了减少了耦合增加了扩展性。
还有一点需要注意的就是我们的observer
都一个通用的功能——投影到公告板,这一点在设计原则中也提到了——找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。我们也将这个功能抽离出来。
1 2 3
| public interface DisplayElement { void display(); }
|
程序设计完了,来看看 UML 图 :
再来看具体的实现 :
气象站
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class WeatherData implements Observable { private float temperature; private float humidity; private float pressure; private List<Observer> observers; private boolean changed;
public WeatherData() { observers = new ArrayList<Observer>(); }
public void setWeatherData(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); }
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
public float getPressure() { return pressure; }
public void setChanged(boolean changed) { this.changed = changed; }
public void clearChanged() { this.changed = false; }
public void measurementsChanged() { System.err.println("数据发生了改变"); notifyObserver(null); }
@Override public void registerObserver(Observer observer) { observers.add(observer); }
@Override public void removeObserver(Observer observer) { int i = observers.indexOf(observer); if (i >= 0) { observers.remove(i); } }
@Override public void notifyObserver(Object arg) { if (!changed) return; for (Observer observer : observers) { observer.update(this, arg); } clearChanged(); } }
|
我们在气象站通过一个List
来管理订阅者们,当数据发生改变时候去通知它们,可以看到完全是面向对象的方式实现的,observable
并不知道也不需要知道它的observer
有哪些功能。至于为什么加一个开关changed
,是因为这样我们可以更好的控制布告板的显示,我们不能每次发生一点改动就去更新它,那样用户的设备会爆炸的(当然没那么严重),有了这个”开关”我们就可以控制它的频率,比如每隔一段时间发送一次,或者变动较大发送一次。
天气数据布告板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class CurrentConditionsDisplay implements Observer, DisplayElement { private Observable observable;
public CurrentConditionsDisplay(Observable observable) { this.observable = observable; observable.registerObserver(this); }
@Override public void update(Observable observable, Object arg) { this.observable = observable; display(); }
@Override public void display() { if (observable instanceof WeatherData) { WeatherData data = (WeatherData) observable; System.err.println("温度" + data.getTemperature() + "湿度" + data.getHumidity() + "气压" + data.getPressure()); } } }
|
我们通过多态来获得observable
的信息,然后调用dispaly()
来显示布告板。
天气预报布告板
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
| public class ForecastDisplay implements Observer, DisplayElement { private float currentPressure = 29.92f; private float lastPressure; private Observable weatherData;
public ForecastDisplay(Observable weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); }
public void display() { System.out.print("Forecast: "); if (currentPressure > lastPressure) { System.out.println("Improving weather on the way!"); } else if (currentPressure == lastPressure) { System.out.println("More of the same"); } else if (currentPressure < lastPressure) { System.out.println("Watch out for cooler, rainy weather"); } }
@Override public void update(Observable observable, Object arg) { if (observable instanceof WeatherData) { lastPressure = currentPressure; currentPressure = ((WeatherData) observable).getPressure(); display(); } } }
|
酷热指数布告板
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
| public class ComfortLevelDisplay implements Observer, DisplayElement { private Observable observable;
public ComfortLevelDisplay(Observable observable) { this.observable = observable; observable.registerObserver(this); }
@Override public void update(Observable observable, Object arg) { this.observable = observable; display(); }
@Override public void display() { if (observable instanceof WeatherData) { double comfortLevel = getComfortLevel(((WeatherData) observable).getTemperature(), ((WeatherData) observable).getHumidity()); System.err.println("酷热指数 : " + comfortLevel); } }
public double getComfortLevel(float t, float rh) { return (16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + 0.000000000843296 * (t * t * rh * rh * rh)) - (0.0000000000481975 * (t * t * t * rh * rh * rh)); } }
|
这一大坨代码就是用来计算”酷热指数的”。至于为什么这么写,你可以问气象局或者百度~。
最后我们来测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Main { public static void main(String[] arg) { WeatherData weatherData = new WeatherData(); ComfortLevelDisplay comfortLevelDisplay = new ComfortLevelDisplay(weatherData); CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setWeatherData(13, 14, 15); weatherData.setChanged(true); weatherData.setWeatherData(15, 16, 16); weatherData.setWeatherData(13, 16, 16); } }
|
我们在气象站中更新了三次数据来看看公告板是如何显示的吧 :
1 2 3 4 5 6
| log: 数据发生了改变 log: 数据发生了改变 酷热指数 : 84.45697418607358 温度15.0 湿度16.0 气压16.0 Forecast: Watch out for cooler, rainy weather log: 数据发生了改变
|
是不是和你预期的一样呢,观察者模式在这里就介绍完了。
为交互对象之间的松耦合设计而努力
要点
- 观察者模式定义了对象之间一对多的关系。
- 主题(也就是可观察者)用一个共同的接口来更新观察者。
- 观察者和观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
- 使用此模式时,你可以从被观察者处推(pus)或拉(pull)数据(推的方式被认为更”正确”)。
- 有多个观察者时,不可以依赖特定的通知次序。
- Java 有多种观察者模式的实现,包括了通用的
java.util.Obseravble
。
day day up