背景

需求案例

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

    基本介绍

    定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态
    主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。

    观察者模式UML


    主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。
    观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。
    设计模式-18-观察者模式 - 图1

    应用示例

    需求

    气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。

    需求分析

    设计模式-18-观察者模式 - 图2

    代码实现

    ```java //接口, 让WeatherData 来实现 public interface Subject {

    public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObservers(); }

  1. ```java
  2. //观察者接口,具体观察者来实现
  3. public interface Observer {
  4. public void update(float temperature, float pressure, float humidity);
  5. }
  1. public class CurrentConditions implements Observer {
  2. // 温度,气压,湿度
  3. private float temperature;
  4. private float pressure;
  5. private float humidity;
  6. // 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
  7. public void update(float temperature, float pressure, float humidity) {
  8. this.temperature = temperature;
  9. this.pressure = pressure;
  10. this.humidity = humidity;
  11. display();
  12. }
  13. // 显示
  14. public void display() {
  15. System.out.println("***Today mTemperature: " + temperature + "***");
  16. System.out.println("***Today mPressure: " + pressure + "***");
  17. System.out.println("***Today mHumidity: " + humidity + "***");
  18. }
  19. }
public class BaiduSite implements Observer {

   // 温度,气压,湿度
   private float temperature;
   private float pressure;
   private float humidity;

   // 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
   public void update(float temperature, float pressure, float humidity) {
      this.temperature = temperature;
      this.pressure = pressure;
      this.humidity = humidity;
      display();
   }

   // 显示
   public void display() {
      System.out.println("===百度网站====");
      System.out.println("***百度网站 气温 : " + temperature + "***");
      System.out.println("***百度网站 气压: " + pressure + "***");
      System.out.println("***百度网站 湿度: " + humidity + "***");
   }

}
/**
 * 该类是核心
 * 1. 包含最新的天气情况信息 
 * 2. 含有 观察者集合,使用ArrayList管理
 * 3. 当数据有更新时,就主动的调用   ArrayList, 通知所有的(接入方)就看到最新的信息
 * @author Administrator
 *
 */
public class WeatherData implements Subject {
   private float temperatrue;
   private float pressure;
   private float humidity;
   //观察者集合
   private ArrayList<Observer> observers;

   //加入新的第三方

   public WeatherData() {
      observers = new ArrayList<Observer>();
   }

   public float getTemperature() {
      return temperatrue;
   }

   public float getPressure() {
      return pressure;
   }

   public float getHumidity() {
      return humidity;
   }

   public void dataChange() {
      //调用 接入方的 update

      notifyObservers();
   }

   //当数据有更新时,就调用 setData
   public void setData(float temperature, float pressure, float humidity) {
      this.temperatrue = temperature;
      this.pressure = pressure;
      this.humidity = humidity;
      //调用dataChange, 将最新的信息 推送给 接入方 currentConditions
      dataChange();
   }

   //注册一个观察者
   @Override
   public void registerObserver(Observer o) {
      // TODO Auto-generated method stub
      observers.add(o);
   }

   //移除一个观察者
   @Override
   public void removeObserver(Observer o) {
      // TODO Auto-generated method stub
      if(observers.contains(o)) {
         observers.remove(o);
      }
   }

   //遍历所有的观察者,并通知
   @Override
   public void notifyObservers() {
      // TODO Auto-generated method stub
      for(int i = 0; i < observers.size(); i++) {
         observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
      }
   }
}
public class Client {

    public static void main(String[] args) {

        //创建一个WeatherData
        WeatherData weatherData = new WeatherData();

        //创建观察者
        CurrentConditions currentConditions = new CurrentConditions();
        BaiduSite baiduSite = new BaiduSite();

        //注册到weatherData
        weatherData.registerObserver(currentConditions);
        weatherData.registerObserver(baiduSite);

        //测试
        System.out.println("通知各个注册的观察者, 看看信息");
        weatherData.setData(10f, 100f, 30.3f);

        System.out.println("=========华丽分割========");
        weatherData.removeObserver(currentConditions);
        weatherData.setData(12f, 200f, 10.3f);

    }

}

image.png

JDK中Observable 类源码分析

进入Observable,其中addObserver相当于注册一个观察者,deleteObserver是移除一个观察者,notifyObserver是通知观察者们,与之前的案例类似
image.png
image.png
进入Observer,发现它是一个接口,里面定义了update方法
image.png

模式角色分析

Observable 的作用和地位等价于 我们前面讲过 Subject
Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理 Observer 的方法 add… delete … notify…
Observer 的作用和地位等价于我们前面讲过的 Observer, 有 update
Observable 和 Observer 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式

监听者模式(观察者的另一种形态)

监听器模式通常包含三个角色:事件源、事件对象、事件监听器。
如果观察者模式中的类名和方法对照改一下,并不改变业务逻辑,我们来看看是啥效果。
比如,将Observer改名为Listener,将其update方法改为onClick();将Subject的实现类ConcreteSubject改名为ListenerSupport,将registerObserver方法更名为addListener……

定义一个事件对象Event,用来传递事件信息:

public class Event { 
    private String data; 
    private String type; 

    Event(String data, String type) { 
        this.data = data; 
        this.type = type; 
    } 
    // 省略getter/setter 
}

原来主题的订阅对象Observer改名为Listener之后,成为监听器:

public interface Listener { 
    void onClick(Event event); 
}

为监听器提供一个实现类,当然在实践中也可以采用匿名类创建的方式:

public class ListenerA implements Listener { 

    @Override 
    public void onClick(Event event) { 
        System.out.println("触发事件,type:" + event.getType() + ",data:" + event.getData()); 
    } 
}

原来的主题ConcreteSubject对照ListenerSupport成为事件管理器:

public class ListenerSupport { 

    private List<Listener> listeners = new ArrayList<>(); 

    public void addListener(Listener listener) { 
        listeners.add(listener); 
    } 

    public void triggerEvent(Event event) { 
        for (Listener listener : listeners) { 
            listener.onClick(event); 
        } 
    } 
}

测试

public class EventTest { 

    public static void main(String[] args) { 
        Listener listener = new ListenerA(); 
        ListenerSupport listenerSupport = new ListenerSupport(); 
        listenerSupport.addListener(listener); 
        listenerSupport.triggerEvent(new Event("dataA", "typeA")); 
    } 
}

通过上面的对照代码,我们可以看出,即便业务逻辑不变,经过重命名的观察者模式已经变为监听器模式了。而它们的对照关系是:事件源对照ConcreteSubject(主题)、事件对象对照update方法的Object、事件监听器对照ConcreteObserver(订阅者)。这也再次证明了所说的“监听器模式是观察者模式的另一种形态”。

观察者模式和监听器模式对比

设计模式-18-观察者模式 - 图7
通过对比可以发监听器模式的优势是:在很多场景中,通知中附带了一些必不可少的其他信息,而事件Event可以对这些信息进行封装,使它本身拥有了多态的特性。每个事件对象就可以包含不同的信息。从这个层面来说,事件监听器模式是对观察者模式进行了进一步的抽象。

Spring中的监听

观察者模式的经典应用算是Spring事件驱动模型了,它便是基于观察者模式实现的,同时也是项目中最常见的事件监听器。
Spring中观察者模式包含四个角色:事件、事件监听器、事件源、事件管理。
事件ApplicationEvent是所有事件对象的父类。ApplicationEvent继承自jdk的EventObject,所有的事件都需要继承ApplicationEvent,并且通过source得到事件源。Spring 提供了很多内置事件,比如:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent。
事件监听器ApplicationListener,也就是观察者,继承自jdk的EventListener,该类中只有一个方法onApplicationEvent,当监听的事件发生后该方法会被执行。
事件源ApplicationContext,ApplicationContext是Spring的核心容器,在事件监听中ApplicationContext可以作为事件的发布者,也就是事件源。因为ApplicationContext继承自ApplicationEventPublisher。在ApplicationEventPublisher中定义了事件发布的方法:publishEvent(Object event)。
事件管理ApplicationEventMulticaster,用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把Applicationcontext发布的Event广播给它的监听器列表。