在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新
代码实现
观察者模式应该是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