观察者模式(Observer Pattern)

一、观察者模式内容

观察者模式在对象之间定义了一对多的依赖,这样一来,依赖它的对象都会受到通知并自动更新。(接口回调的一种方式,观察者在同一个被观察者对象中注册自己的信息(引用),当被观察者发生变化时,调用观察者的特定方法,告诉观察者。)

观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其它的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作(Collaboration)。观察者模式是满足这一要求的各种设计方案中最重要的一种。

二、要点

  • 观察者模式定义了对象之间一对多的关系。
  • 主题(也就是可观察者)用一个共同的接口来更新观察者。
  • 观察者和可观察者之间用松耦合方式结合(loosencoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
  • 使用此模式时,你可以从观察者处推或拉数据(“推”的方式被认为更正确)。
  • 有多个观察者时,不可以依赖特定的通知次序。
  • 要注意java.util.Observable实现上所带来的问题。(Observable是一类,而不是以接口)。
  • 如果有必要的话,可以实现自己的Observable,这并不难。
  • Swing大量使用观察者模式,需要GUI框架也是如此。此模式在JavaBeans、RMI当中也被应用。

三、观察者模式结构


四、观察者模式示例代码

可观察者接口

  1. public interface Subject {  
  2.     public void registerObserver(Observer o);  
  3.     public void removeObserver(Observer o);  
  4.     public void notifyObservers();  
  5. }  
可观察者接口实现
  1. package com.yang.observer;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5. public class WeatherData implements Subject {  
  6.     private ArrayList observers;  
  7.     private float temperature;  
  8.     private float humidity;  
  9.     private float pressure;  
  10.       
  11.     public WeatherData() {  
  12.         observers = new ArrayList();  
  13.     }  
  14.       
  15.     public void registerObserver(Observer o) {  
  16.         observers.add(o);  
  17.     }  
  18.       
  19.     public void removeObserver(Observer o) {  
  20.         int i = observers.indexOf(o);  
  21.         if (i >= 0) {  
  22.             observers.remove(i);  
  23.         }  
  24.     }  
  25.       
  26.     public void notifyObservers() {  
  27.         for (int i = 0; i < observers.size(); i++) {  
  28.             Observer observer = (Observer)observers.get(i);  
  29.             observer.update(temperature, humidity, pressure);  
  30.         }  
  31.     }  
  32.       
  33.     public void measurementsChanged() {  
  34.         notifyObservers();  
  35.     }  
  36.       
  37.     public void setMeasurements(float temperature, float humidity, float pressure) {  
  38.         this.temperature = temperature;  
  39.         this.humidity = humidity;  
  40.         this.pressure = pressure;  
  41.         measurementsChanged();  
  42.     }  
  43.       
  44.     // other WeatherData methods here  
  45.       
  46.     public float getTemperature() {  
  47.         return temperature;  
  48.     }  
  49.       
  50.     public float getHumidity() {  
  51.         return humidity;  
  52.     }  
  53.       
  54.     public float getPressure() {  
  55.         return pressure;  
  56.     }  
  57. }  

观察者接口
  1. package com.yang.observer;  
  2.   
  3. public interface Observer {  
  4.     public void update(float temp, float humidity, float pressure);  
  5. }  

观察者接口实现
  1. package com.yang.observer;  
  2.   
  3. public class CurrentConditionsDisplay implements Observer, DisplayElement {  
  4.     private float temperature;  
  5.     private float humidity;  
  6.     private Subject weatherData;  
  7.       
  8.     public CurrentConditionsDisplay(Subject weatherData) {  
  9.         this.weatherData = weatherData;  
  10.         weatherData.registerObserver(this);  
  11.     }  
  12.       
  13.     public void update(float temperature, float humidity, float pressure) {  
  14.         this.temperature = temperature;  
  15.         this.humidity = humidity;  
  16.         display();  
  17.     }  
  18.       
  19.     public void display() {  
  20.         System.out.println("Current conditions: " + temperature   
  21.             + "F degrees and " + humidity + "% humidity");  
  22.     }  
  23. }  

  1. package com.yang.observer;  
  2.   
  3. public class StatisticsDisplay implements Observer, DisplayElement {  
  4.     private float maxTemp = 0.0f;  
  5.     private float minTemp = 200;  
  6.     private float tempSum= 0.0f;  
  7.     private int numReadings;  
  8.     private WeatherData weatherData;  
  9.   
  10.     public StatisticsDisplay(WeatherData weatherData) {  
  11.         this.weatherData = weatherData;  
  12.         weatherData.registerObserver(this);  
  13.     }  
  14.   
  15.     public void update(float temp, float humidity, float pressure) {  
  16.         tempSum += temp;  
  17.         numReadings++;  
  18.   
  19.         if (temp > maxTemp) {  
  20.             maxTemp = temp;  
  21.         }  
  22.    
  23.         if (temp < minTemp) {  
  24.             minTemp = temp;  
  25.         }  
  26.   
  27.         display();  
  28.     }  
  29.   
  30.     public void display() {  
  31.         System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)  
  32.             + "/" + maxTemp + "/" + minTemp);  
  33.     }  
  34. }  

  1. package com.yang.observer;  
  2.   
  3. public class ForecastDisplay implements Observer, DisplayElement {  
  4.     private float currentPressure = 29.92f;    
  5.     private float lastPressure;  
  6.     private WeatherData weatherData;  
  7.   
  8.     public ForecastDisplay(WeatherData weatherData) {  
  9.         this.weatherData = weatherData;  
  10.         weatherData.registerObserver(this);  
  11.     }  
  12.   
  13.     public void update(float temp, float humidity, float pressure) {  
  14.                 lastPressure = currentPressure;  
  15.         currentPressure = pressure;  
  16.   
  17.         display();  
  18.     }  
  19.   
  20.     public void display() {  
  21.         System.out.print("Forecast: ");  
  22.         if (currentPressure > lastPressure) {  
  23.             System.out.println("Improving weather on the way!");  
  24.         } else if (currentPressure == lastPressure) {  
  25.             System.out.println("More of the same");  
  26.         } else if (currentPressure < lastPressure) {  
  27.             System.out.println("Watch out for cooler, rainy weather");  
  28.         }  
  29.     }  
  30. }  

观察者实现的另外一显示接口
  1. package com.yang.observer;  
  2.   
  3. public interface DisplayElement {  
  4.     public void display();  
  5. }  

测试

  1. package com.yang.observer;  
  2.   
  3. public class WeatherStation {  
  4.   
  5.     public static void main(String[] args) {  
  6.         WeatherData weatherData = new WeatherData();  
  7.       
  8.         CurrentConditionsDisplay currentDisplay =   
  9.             new CurrentConditionsDisplay(weatherData);  
  10.         StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);  
  11.         ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);  
  12.   
  13.         weatherData.setMeasurements(806530.4f);  
  14.         weatherData.setMeasurements(827029.2f);  
  15.         weatherData.setMeasurements(789029.2f);  
  16.     }  
  17. }  

五、使用JDK当中的Observable实现

可观察者
  1. import java.util.Observable;  
  2. import java.util.Observer;  
  3.       
  4. public class WeatherData extends Observable {  
  5.     private float temperature;  
  6.     private float humidity;  
  7.     private float pressure;  
  8.     public WeatherData() { }  
  9.     public void measurementsChanged() {  
  10.         setChanged();//注意此处在更新观察者时,更有弹性  
  11.         notifyObservers();  
  12.     }  
  13.     public void setMeasurements(float temperature, float humidity, float pressure) {  
  14.         this.temperature = temperature;  
  15.         this.humidity = humidity;  
  16.         this.pressure = pressure;  
  17.         measurementsChanged();  
  18.     }  
  19.     public float getTemperature() {  
  20.         return temperature;  
  21.     }  
  22.     public float getHumidity() {  
  23.         return humidity;  
  24.     }  
  25.     public float getPressure() {  
  26.         return pressure;  
  27.     }  
  28. }  
显示接口
  1. public interface DisplayElement {  
  2.     public void display();  
  3. }  
观察者
  1. import java.util.Observable;  
  2. import java.util.Observer;  
  3.   
  4. public class ForecastDisplay implements Observer, DisplayElement {  
  5.     private float currentPressure = 29.92f;    
  6.     private float lastPressure;  
  7.   
  8.     public ForecastDisplay(Observable observable) {  
  9.         observable.addObserver(this);  
  10.     }  
  11.   
  12.     public void update(Observable observable, Object arg) {  
  13.         if (observable instanceof WeatherData) {  
  14.             WeatherData weatherData = (WeatherData)observable;  
  15.             lastPressure = currentPressure;  
  16.             currentPressure = weatherData.getPressure();  
  17.             display();  
  18.         }  
  19.     }  
  20.   
  21.     public void display() {  
  22.         System.out.print("Forecast: ");  
  23.         if (currentPressure > lastPressure) {  
  24.             System.out.println("Improving weather on the way!");  
  25.         } else if (currentPressure == lastPressure) {  
  26.             System.out.println("More of the same");  
  27.         } else if (currentPressure < lastPressure) {  
  28.             System.out.println("Watch out for cooler, rainy weather");  
  29.         }  
  30.     }  
  31. }  

  1. import java.util.Observable;  
  2. import java.util.Observer;  
  3.   
  4. public class StatisticsDisplay implements Observer, DisplayElement {  
  5.     private float maxTemp = 0.0f;  
  6.     private float minTemp = 200;  
  7.     private float tempSum= 0.0f;  
  8.     private int numReadings;  
  9.   
  10.     public StatisticsDisplay(Observable observable) {  
  11.         observable.addObserver(this);  
  12.     }  
  13.   
  14.     public void update(Observable observable, Object arg) {  
  15.         if (observable instanceof WeatherData) {  
  16.             WeatherData weatherData = (WeatherData)observable;  
  17.             float temp = weatherData.getTemperature();  
  18.             tempSum += temp;  
  19.             numReadings++;  
  20.   
  21.             if (temp > maxTemp) {  
  22.                 maxTemp = temp;  
  23.             }  
  24.    
  25.             if (temp < minTemp) {  
  26.                 minTemp = temp;  
  27.             }  
  28.   
  29.             display();  
  30.         }  
  31.     }  
  32.   
  33.     public void display() {  
  34.         System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)  
  35.             + "/" + maxTemp + "/" + minTemp);  
  36.     }  
  37. }  
  1. import java.util.Observable;  
  2. import java.util.Observer;  
  3.       
  4. public class CurrentConditionsDisplay implements Observer, DisplayElement {  
  5.     Observable observable;  
  6.     private float temperature;  
  7.     private float humidity;  
  8.       
  9.     public CurrentConditionsDisplay(Observable observable) {  
  10.         this.observable = observable;  
  11.         observable.addObserver(this);  
  12.     }  
  13.       
  14.     public void update(Observable obs, Object arg) {  
  15.         if (obs instanceof WeatherData) {  
  16.             WeatherData weatherData = (WeatherData)obs;  
  17.             this.temperature = weatherData.getTemperature();  
  18.             this.humidity = weatherData.getHumidity();  
  19.             display();  
  20.         }  
  21.     }  
  22.       
  23.     public void display() {  
  24.         System.out.println("Current conditions: " + temperature   
  25.             + "F degrees and " + humidity + "% humidity");  
  26.     }  
  27. }  
测试
  1. public class WeatherStation {  
  2.   
  3.     public static void main(String[] args) {  
  4.         WeatherData weatherData = new WeatherData();  
  5.         CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData);  
  6.         StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);  
  7.         ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);  
  8.   
  9.         weatherData.setMeasurements(806530.4f);  
  10.         weatherData.setMeasurements(827029.2f);  
  11.         weatherData.setMeasurements(789029.2f);  
  12.     }  
  13. }  

六、使用到的OO原则

  • 封装变化
在观察者模式中,会改变的主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,而不必改变主题,这就叫提前规划。
  • 多用组合,少用继承
观察者模式利用“组合”将许多观察者组合进入主题中,对象之间的这种关系不是通过继承产生的,而是在运行时利用组合方式而产生的。
  • 针对接口编程,不针对实现编程
主题与观察者都使用接口:观察者使用接口向主题注册,而主题利用观察者接口通知观察者,这样可以让两者之间运作正常,同时具有松耦合的有点。
  • 为交互对象之间的松耦合设计而努力

七、 观察者模式的优缺点

Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口,使得可以有各种各样不同的表示层(观察者)。

但是其缺点是每个外观对象必须继承这个抽像出来的接口类,这样就造成了一些不方便,比如有一个别人写的外观对象,并没有继承该抽象类,或者接口不对,我们又希望不修改该类直接使用它。虽然可以再应用Adapter模式来一定程度上解决这个问题,但是会造成更加复杂烦琐的设计,增加出错几率。

观察者模式的效果有以下几个优点:

(1)观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体现察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

(2)观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。

观察者模式有下面的一些缺点:

(1)如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

(2)如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。

(3)如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。

(4)虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。

八、观察者模式在JDK当中的应用

Observer (or Publish/Subscribe) (recognizeable by behavioral methods which invokes a method on an instance of another abstract/interface type, depending on own state)

九、参考文献

 
 
原文地址:https://www.cnblogs.com/hujihon/p/3729767.html