设计模式之观察者模式

《Head First 设计模式》阅读笔记

Posted by wangtiegang on August 18, 2019

这两天学习了观察者模式,看完书之后,发现观察者模式其实很简单,书中的定义为

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

这个就好比订阅报纸,当报社有新报纸出版时,就会通知所有订阅用户,并且把报纸送到对方手中,任何时候都可以有新的订阅者加入,也可以有老的订阅者退出。 这里的报社称为 可观察者/主题 ,订阅用户称为 观察者。主题跟观察者之间解藕,不需要关心观察者是什么类型,观察者也可以同时订阅多个主题。要实现两者之间的解藕,关键在于设计原则中的 面向接口编程

主题需要定义一个接口

  • 包含一个注册观察者的方法
  • 包含一个移除观察者的方法
  • 包含一个通知观察者的方法

观察者需要定义一个接口

  • 接收通知自动更新状态的方法

具体的实现代码如下:

  • 主题接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    public interface CustObservable {
    
      /**
       * 注册观察者
       * @param observer
       */
      void registerObserver(CustObserver observer);
    
      /**
       * 移除观察者
       * @param observer
       */
      void removeObserver(CustObserver observer);
    
      /**
       * 通知所有观察者
       */
      void notifyObservers();
    
    }
    
  • 主题实现

    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
    
      public class CustWeatherData implements CustObservable{
    
      /**
       * 观察者对象
       */
      private List<CustObserver> observers;
    
      public CustWeatherData(){
          observers = new ArrayList<>();
      }
    
      /**
       * 注册观察者
       * @param observer
       */
      @Override
      public void registerObserver(CustObserver observer) {
          this.observers.add(observer);
          System.out.println("添加观察者"+observer);
      }
    
      /**
       * 移除观察者
       * @param observer
       */
      @Override
      public void removeObserver(CustObserver observer) {
          int i = this.observers.indexOf(observer);
          if (i > 0){
              this.observers.remove(observer);
              System.out.println("移除观察者"+observer);
          }
      }
    
      /**
       * 通知所有观察者
       */
      @Override
      public void notifyObservers() {
          if(this.observers.size() > 0){
              for (CustObserver observer : observers){
                  observer.update(new HashMap());
              }
          }
      }
    
      /**
       * 可观察者状态发生变化
       */
      public void statusChanged(){
          notifyObservers();
      }
    
    }
    
  • 观察者接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public interface CustObserver {
    
      /**
       * 接收通知,更新状态
       * @param data
       */
      void update(Map data);
    
    }
    
  • 观察者实现

    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
    
    public class CustObserver1 implements CustObserver {
    
      @Override
      public void update(Map data) {
          System.out.println("观察者_1_收到状态更新");
      }
    
    }
    
    public class CustObserver2 implements CustObserver {
    
      @Override
      public void update(Map data) {
          System.out.println("观察者_2_收到状态更新");
      }
    
    }
    
    public class CustObserver3 implements CustObserver {
    
      @Override
      public void update(Map data) {
          System.out.println("观察者_3_收到状态更新");
      }
    
    }
    
  • Test

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      ublic static void main(String[] args) {
          // 实例化主题
          CustWeatherData weatherData = new CustWeatherData();
          // 注册观察者
          weatherData.registerObserver(new CustObserver1());
          CustObserver2 custObserver2 = new CustObserver2();
          weatherData.registerObserver(custObserver2);
          weatherData.registerObserver(new CustObserver3());
          // 通知
          weatherData.statusChanged();
          // 移除观察者2
          weatherData.removeObserver(custObserver2);
          // 通知
          weatherData.statusChanged();
      }
    

上面就是观察者模式的一个基本实现,还可以根据实际情况进行优化,比如书中提到了观察者模式的消息可以改造成“推”和“拉”,因为一个主题可能有很多状态,但是某些观察者并不想获取所有状态,如果是“推”,则把所有状态都推送过去了,如果改造成“拉”,则在观察者收到更新提醒后,可以按需获取部分信息。在上面“推”的例子上做些改造:

  • 主题增加一系列getter方法,通知观察者时把自己传过去

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      public String getData1(){
          return "数据_1";
      }
    
      public String getData2(){
          return "数据_2";
      }
    
      public String getData3(){
          return "数据_3";
      }
    
    1
    2
    3
    4
    5
    6
    7
    
      public void notifyObservers() {
          if(this.observers.size() > 0){
              for (CustObserver observer : observers){
                  observer.update(this,new HashMap());
              }
          }
      }
    
  • 观察者接收到通知后,通过主题对象获取需要到信息

    1
    2
    3
    4
    5
    
      public void update(Object object,Map data) {
          if(object instanceof CustWeatherData){
              System.out.println("观察者_1_收到状态更新:" + ((CustWeatherData) object).getData1());
          }
      }
    

java在jdk中内置了观察者模式,可以在实际运用中看情况选择使用,其中主题需要实现 java.util.Observable 类,该类中提供了注册移除等方法,也有存储观察者的集合对象,不同的地方在于它提供了一个 setChanged() 方法,在通知观察者前需要先调用此方法,将标志重置为 true ,这是因为考虑到有时候我们并不想在主题数据一变化就立即通知观察者,从而提供此方法让我们可以手动控制什么条件下推送消息。同样观察者需要实现 java.util.Observer 接口。

书中认为内置的观察者模式违法了设计原则:面向接口编程。因为 java.util.Observable 是一个类,而不是一个接口,这是面向实现编程。而且该类没有实现接口,所以没办法建立自己的实现,同时由于单继承,继承了该类就没办法再继承其他类。所以在使用的时候得结合实际情况考虑,如果不满足需求的话,则可以自己实现观察者模式,好在这个模式本身非常简单。