设计模式学习笔记(15)观察者

本文实例代码:https://github.com/JamesZBL/java_design_patterns

观察者(Observer)模式实现了一种一对多的通信方式,它的核心是由多个对象监听中心对象,中心对象发生某种变化时将这个变化通知给关注它的周边对象,所以,它又被称为 “订阅-发送” 模式。

在现实生活中也有许多和观察者模式的运作方式相似的例子,比如当我们订阅某个公众号后,一旦该公众号的主人发不了新的文章或消息,所有订阅这个公众号的用户都会受到这个消息。这些用户就对应着观察者模式里的订阅者,公众号对应着发布者,一旦订阅者对关注点失去了兴趣,就会取消对发布者的订阅,也就不会再受到发布者的任何消息了。所以,实现观察者模式的主要方法就是订阅和发布。

实例

我们的祖国地大物博,南方人和北方人的生活习惯在某些方面的差异是很大的,这一点饮食习惯上可以非常容易看出来。所以我们现在用一天中不同时段,南方人和北方人的饮食活动举个例子。

现在有一个智能闹钟,南方人和北方人都在这个闹钟上定时,每到特定时刻,这个闹钟就会分别提醒它的用户。所以,用户要统一实现一个接口,以便闹钟能够调用用户中的方法来通知用户,这个接口同时代表了订阅者的关注点,即时间的变化。首先定义一个枚举类来表示不同的时间点:

TimePoint.java

public enum TimePoint {
  MORNING("早晨"), NOON("中午"), AFTERNOON("下午"), EVENING("晚上");
  private String name;

  TimePoint(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return name;
  }
}

所有闹钟的用户都要实现的接口 TimeObserver,通过接口实现了发布者向订阅者传递消息:

TimeObserver.java

public interface TimeObserver {

  void update(TimePoint time);
}

南方的朋友和北方的朋友分别实现 TimeObserver 接口:

Southern.java

public class Southern implements TimeObserver {

  private static final Logger LOGGER = LoggerFactory.getLogger(Southern.class);

  @Override
  public void update(TimePoint time) {

    LOGGER.info("南方的朋友吃饭了");
    switch (time) {
      case MORNING:
        LOGGER.info("热干面");
        break;
      case NOON:
        LOGGER.info("米饭");
        break;
      case AFTERNOON:
        LOGGER.info("茶");
        break;
      case EVENING:
        LOGGER.info("鱼");
        break;
      default:
        break;
    }
  }
}

Northern.java

public class Northern implements TimeObserver {

  private static final Logger LOGGER = LoggerFactory.getLogger(Northern.class);

  @Override
  public void update(TimePoint time) {

    LOGGER.info("北方人吃饭了");
    switch (time) {
      case MORNING:
        LOGGER.info("煎饼果子");
        break;
      case NOON:
        LOGGER.info("炸酱面");
        break;
      case AFTERNOON:
        LOGGER.info("牛奶");
        break;
      case EVENING:
        LOGGER.info("包子");
        break;
      default:
        break;
    }
  }
}

下面开始定义时间点消息的发布者 Time,也就是上文中的闹钟:

Time.java

public class Time {

  private static final Logger LOGGER = LoggerFactory.getLogger(Time.class);

  private TimePoint point;
  private List<TimeObserver> observers;

  public Time() {
    this.point = TimePoint.MORNING;
    observers = new ArrayList<>();
  }

  public void addObserver(TimeObserver observer) {
    observers.add(observer);
  }

  public void removeObserver(TimeObserver observer) {
    observers.remove(observer);
  }

  public void passing() {
    TimePoint[] points = TimePoint.values();
    point = points[(point.ordinal() + 1) % points.length];
    LOGGER.info("时间来到了{}", point);
    notifyObservers();
  }

  public void notifyObservers() {
    for (TimeObserver observer : observers) {
      observer.update(point);
    }
  }
}

Time 对象持有一系列 TimeObserver 对象的引用,每当时间点发生变化, Time 就会通过调用 TimeObserverupdate() 方法,下面模拟一下这个闹钟的使用过程:

App.java

public class Application {

  public static void main(String[] args) {
    Time time = new Time();
    time.addObserver(new Northern());
    time.addObserver(new Southern());

    time.passing();
    time.passing();
    time.passing();
    time.passing();
    time.passing();
    time.passing();
    time.passing();
    time.passing();
    time.passing();
  }
}

总结

这是整个包的类图

观察者模式的具体应用形式可能多种多样,但都包括以下几点共性:

  • 发布者(被观察方)和订阅者(观察方)通常是一对多的关系,即发布者可以同时被多个订阅者订阅,所以比较常用的方式是用发布者用集合的方式维护一系列订阅者
  • 可以通过控制发布者的实现细节来控制最终是否向某个订阅者发布消息
  • 订阅者的关注点发生变化时,发布者通过接口回调来通知订阅者

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.