首页 > 技术文章 > 17.java设计模式之观察者模式

xiaokantianse 2020-12-03 18:10 原文

基本需求

  • 气象站可以将每天测量到的温度,湿度,气压等等,以公告的形式发布出去(比如发布到自己的网站或第三方)
  • 需要设计开放型API,便于其他第三方也能接入气象站获取数据
  • 提供温度、气压和湿度的接口
  • 测量数据更新时,要能实时的通知给第三方

传统方案

  • 通过对需求的分析,我们可以设计一个WeatherData类,其中包含getXxx()方法,可以让第三方接入,并得到相关信息

  • 当有数据更新时,WeatherData调用dataChange()方法去更新数据,当第三方获取时,就能获取到最新的数据,当然也可以推送

  • WeatherData内部还需维护一个CurrentCondition对象,便于完成推送或通知

  • UML类图

  • 代码实现

    • public class CurrentCondition {
      
         // 被通知的对象类
      
         // 温度
         private double temperature;
         // 压力
         private double pressure;
         // 湿度
         private double humidity;
      
         public void update(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             display();
         }
      
         private void display() {
             System.out.println("======CurrentCondition======");
             System.out.println("温度:" + this.temperature);
             System.out.println("压力:" + this.pressure);
             System.out.println("湿度:" + this.humidity);
         }
      
      }
      
    • public class WeatherData {
      
         // 天气数据类
      
         // 温度
         private double temperature;
         // 压力
         private double pressure;
         // 湿度
         private double humidity;
      
         // 聚合天气数据发生改变需要通知或者被推送的类
         private CurrentCondition currentCondition;
      
         public WeatherData(CurrentCondition currentCondition) {
             this.currentCondition = currentCondition;
         }
      
         // 将天气数据的改变通知给需要通知或者被推送的类 被通知的类也可以通过getXxx()方法自己获取数据
         private void dataChange() {
             currentCondition.update(this.temperature, this.pressure, this.humidity);
         }
      
         // 更改天气数据
         public void setData(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             dataChange();
         }
      
      }
      
    • public class Client {
         public static void main(String[] args) {
             WeatherData weatherData = new WeatherData(new CurrentCondition());
             // 更新天气数据 就会通知应用或者第三方
             weatherData.setData(10d, 20d, 30d);
             System.out.println("======天气情况发生变换======");
             weatherData.setData(20d, 30d, 40d);
         }
      }
      
    • 问题分析

      • 其他第三方接入气象站获取数据问题
      • 无法在运行时动态的添加第三方
      • 违反了ocp原则,在WeatherData中,如果需要增加新的第三方应用,都需要创建一个对应的第三方的公告板对象,并加入到dataChange()方法中,不利于维护,也不是动态加入

基本介绍

  • 当对象间存在一对多关系时,则使用观察者模式(Observer),比如,当一个对象被修改时,则会自动通知依赖它的对象,观察者模式属于行为型模式

  • 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

  • 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知

  • 基本原理

    • 观察者模式类似订牛奶业务 例如:奶站/气象局:Subject 和 用户/第三方网站:Observer
    • Subject:登记注册、移除和通知 一的一方(被观察者)
      • registerObserver 注册
      • removeObserver 移除
      • notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定
    • Observer:接收输入 也就是第三方,可以有多个实现 多个一方(观察者)
    • 对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户时Observer,是多的一方
  • UML类图(案例)

  • 代码实现

    • public interface Subject {
      
         // 被观察者接口 作为一的一方 ,需要聚合多个观察者 可以使用List进行管理
      
         // 注册观察者
         void register(Observer observer);
      
         // 移除观察者
         void remove(Observer observer);
      
         // 被观察者状态发生改变,进行通知所有的观察者
         void notifyObservers();
      
      }
      
      // 实现类
      class WeatherData implements Subject {
      
         // 天气数据类 作为被观察者 一的一方
      
         // 温度
         private double temperature;
         // 压力
         private double pressure;
         // 湿度
         private double humidity;
      
         // 聚合所有的观察者进行管理 动态进行修改 使用List集合管理观察者们
         private List<Observer> observers;
      
         public WeatherData() {
             this.observers = new ArrayList<Observer>();
         }
      
         // 更改天气数据
         public void setData(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             // 被观察者状态发生改变,通知注册的所有观察者
             notifyObservers();
         }
      
         @Override
         public void register(Observer observer) {
             if (!this.observers.contains(observer)) {
                 observers.add(observer);
             }
         }
      
         @Override
         public void remove(Observer observer) {
             this.observers.remove(observer);
         }
      
         @Override
         public void notifyObservers() {
             // 遍历观察者的集合 对所有的观察者进行通知
             observers.forEach(observer -> observer.update(this.temperature, this.pressure, this.humidity));
         }
      
      }
      
      
    • public interface Observer {
      
         // 观察者接口 作为多的一方
      
         // 被观察者改变状态时,通知观察者所调用的方法
         void update(double temperature, double pressure, double humidity);
      
      }
      
      // 子类一
      class CurrentCondition implements Observer{
      
         // 被通知的对象类
      
         // 温度
         private double temperature;
         // 压力
         private double pressure;
         // 湿度
         private double humidity;
      
         public void update(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             display();
         }
      
         private void display() {
             System.out.println("======CurrentCondition======");
             System.out.println("温度:" + this.temperature);
             System.out.println("压力:" + this.pressure);
             System.out.println("湿度:" + this.humidity);
         }
      
      }
      
      // 子类二
      class BaiDu implements Observer{
      
         // 被通知的对象类
      
         // 温度
         private double temperature;
         // 压力
         private double pressure;
         // 湿度
         private double humidity;
      
         public void update(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             display();
         }
      
         private void display() {
             System.out.println("======BaiDu======");
             System.out.println("温度:" + this.temperature);
             System.out.println("压力:" + this.pressure);
             System.out.println("湿度:" + this.humidity);
         }
      
      }
      
    • public class Client {
         public static void main(String[] args) {
             // 创建观察者
             CurrentCondition currentCondition = new CurrentCondition();
             // 创建被观察者
             WeatherData weatherData = new WeatherData();
             // 向被观察者中注册观察者
             weatherData.register(currentCondition);
             // 改变被观察者的状态 观察者就会收到通知
             weatherData.setData(10d, 20d, 30d);
      
             // 后续业务扩展 加入新的第三方
             BaiDu baiDu = new BaiDu();
             weatherData.register(baiDu);
             System.out.println("======加入了新的第三方======");
             weatherData.setData(20d, 30d, 40d);
         }
      }
      

jdk源码

  • 在jdk的Observer类和Observable类就使用到了观察者模式

  • Observer作为了观察者接口,提供了update()方法

  • Observable相当于Subject,作为被观察者,只不过在jdk源码中,Observable是一个类,而不是接口,提供了注册观察者,删除观察者,通知观察者的一系列管理观察者的方法

  • // 观察者接口
    public interface Observer {
       /**
        * This method is called whenever the observed object is changed. An
        * application calls an <tt>Observable</tt> object's
        * <code>notifyObservers</code> method to have all the object's
        * observers notified of the change.
        *
        * @param   o     the observable object.
        * @param   arg   an argument passed to the <code>notifyObservers</code>
        *                 method.
        */
       void update(Observable o, Object arg);
    }
    
    // 被观察者 相当于Subject
    public class Observable {
       private boolean changed = false;
       private Vector<Observer> obs;
    
       /** Construct an Observable with zero Observers. */
    
       public synchronized void addObserver(Observer o) {
           if (o == null)
               throw new NullPointerException();
           if (!obs.contains(o)) {
               obs.addElement(o);
           }
       }
    
       /**
        * Deletes an observer from the set of observers of this object.
        * Passing <CODE>null</CODE> to this method will have no effect.
        * @param   o   the observer to be deleted.
        */
       public synchronized void deleteObserver(Observer o) {
           obs.removeElement(o);
       }
    
       /**
        * If this object has changed, as indicated by the
        * <code>hasChanged</code> method, then notify all of its observers
        * and then call the <code>clearChanged</code> method to
        * indicate that this object has no longer changed.
        * <p>
        * Each observer has its <code>update</code> method called with two
        * arguments: this observable object and <code>null</code>. In other
        * words, this method is equivalent to:
        * <blockquote><tt>
        * notifyObservers(null)</tt></blockquote>
        *
        * @see     java.util.Observable#clearChanged()
        * @see     java.util.Observable#hasChanged()
        * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
        */
       public void notifyObservers() {
           notifyObservers(null);
       }
    
       /**
        * If this object has changed, as indicated by the
        * <code>hasChanged</code> method, then notify all of its observers
        * and then call the <code>clearChanged</code> method to indicate
        * that this object has no longer changed.
        * <p>
        * Each observer has its <code>update</code> method called with two
        * arguments: this observable object and the <code>arg</code> argument.
        *
        * @param   arg   any object.
        * @see     java.util.Observable#clearChanged()
        * @see     java.util.Observable#hasChanged()
        * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
        */
       public void notifyObservers(Object arg) {
           /*
            * a temporary array buffer, used as a snapshot of the state of
            * current Observers.
            */
           Object[] arrLocal;
    
           synchronized (this) {
               /* We don't want the Observer doing callbacks into
                * arbitrary code while holding its own Monitor.
                * The code where we extract each Observable from
                * the Vector and store the state of the Observer
                * needs synchronization, but notifying observers
                * does not (should not).  The worst result of any
                * potential race-condition here is that:
                * 1) a newly-added Observer will miss a
                *   notification in progress
                * 2) a recently unregistered Observer will be
                *   wrongly notified when it doesn't care
                */
               if (!changed)
                   return;
               arrLocal = obs.toArray();
               clearChanged();
           }
    
           for (int i = arrLocal.length-1; i>=0; i--)
               ((Observer)arrLocal[i]).update(this, arg);
       }
    }
    

注意事项

  • 观察者和被观察者是抽象耦合的,建立一套触发机制
  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
  • JAVA中已经有了对观察者模式的支持类
  • 避免循环引用
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式

推荐阅读