什么是观察者模式

在《设计模式-可复用面向对象软件基础》一书中,对于观察者模式是这样定义的: 定义对象间的一种一对多的依赖关系 ,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
如果你之前对设计模式没有任何了解的话,直接理解上面的定义比较困难,接下来以一个例子来说明观察者模式是解决什么样的问题。

进一步解释定义

四川盆地天气多变,每天上班前我都要根据天气情况来决定是不是应该带雨伞,需不需要准备遮阳帽。所以,我订阅了一个天气通知的服务,在每天早上 7 点钟该服务会告知我当天的天气情况,我根据该信息可以做出自己的决策(是否带伞,是否戴帽)。

这个例子就可以套用观察者模式。观察者模式的核心为:订阅、发布,所以它又被成为发布-订阅模式。在该例子中,“我订阅了一个天气通知的服务”就对应着 订阅 ;“每天早上 7 点钟告知我当天的天气情况”就对应着 发布 。我们习惯将 订阅的对象 称呼为主题(Subject),而 发布的 就是主题的状态(State)。

然后,我们再来尝试理解观察者模式的定义:

  • 定义对象间的一种一对多的依赖关系:这里的对象有两类,一类为天气预报这样的服务,一类为像我这样需要天气预报的人;一对多的依赖关系指的是多个像我这样的人都需要天气预报
  • 当一个对象的状态发生改变时:这里的对象指的是每天的天气预报,状态就可以理解为晴天、雨天、阴天等天气状态
  • 所有依赖于它的对象都能得到通知并被自动更新:这里的对象指的是像我这样订阅了天气预报通知的人

这下应该对观察者模式的定义有了一个比较清晰地认识了。下面看一下类图。

类图

观察者模式定义了一种解决方案,这种解决方案的类图不是唯一的,在有些细节方面可能与其他地方的内容不一样,这很容易理解,具体场景具体分析,但大致思路是一致的。
行为型 - 观察者模式(Observer) - 图1

代码实现

定义主题接口、观察者接口

  1. public interface Subject {
  2. /**
  3. * 注册观察者
  4. * @param observer observer
  5. */
  6. void registerObserver(Observer observer);
  7. /**
  8. * 注销观察者
  9. * @param observer observer
  10. */
  11. void unregisterObserver(Observer observer);
  12. /**
  13. * 当主题状态变化时,通知所有观察者
  14. */
  15. void notifyObservers();
  16. }
  1. public interface Observer {
  2. /**
  3. * 每当观察到的对象发生变化时,都会调用此方法
  4. * @param o 主题状态改变时通知的消息对象
  5. */
  6. void update(Object o);
  7. }

具体主题实现

  1. public class Weather implements Subject{
  2. private Object state;
  3. private final List<Observer> observers = new ArrayList<Observer>();
  4. /**
  5. * 更新主题状态
  6. * @param state 消息
  7. */
  8. public void setState(String state) {
  9. this.state = state;
  10. this.notifyObservers();
  11. }
  12. @Override
  13. public void registerObserver(Observer observer) {
  14. this.observers.add(observer);
  15. }
  16. @Override
  17. public void unregisterObserver(Observer observer) {
  18. this.observers.remove(observer);
  19. }
  20. @Override
  21. public void notifyObservers() {
  22. this.observers.forEach(o -> o.update(state));
  23. }
  24. }

具体观察者

  1. public class Tom implements Observer {
  2. public Tom(Subject subject) {
  3. subject.registerObserver(this);
  4. }
  5. @Override
  6. public void update(Object o) {
  7. String o1 = (String) o;
  8. String action = o1.contains("下雨") ? "淋着雨上班" : "空手出门";
  9. System.out.println(MessageFormat.format(" Tom 收到消息:{0},{1}", o, action));
  10. }
  11. }
  1. public class Jack implements Observer{
  2. public Jack(Subject subject) {
  3. subject.registerObserver(this);
  4. }
  5. @Override
  6. public void update(Object o) {
  7. String o1 = (String) o;
  8. String action = o1.contains("下雨") ? "今天出门带把伞" : "空手出门";
  9. System.out.println(MessageFormat.format(" Jack 收到消息:{0},{1}", o, action));
  10. }
  11. }

测试代码

  1. public class Client {
  2. public static void main(String[] args) {
  3. // 主题:天气
  4. Weather subject = new Weather();
  5. // 观察者:Jack
  6. new Jack(subject);
  7. // 观察者:Tom
  8. new Tom(subject);
  9. // 主题状态发生变化
  10. subject.setState("2022年5月13日下雨");
  11. subject.setState("2022年5月14日天气晴朗");
  12. }
  13. }
  1. Jack 收到消息:2022513日下雨,今天出门带把伞
  2. Tom 收到消息:2022513日下雨,淋着雨上班
  3. Jack 收到消息:2022514日天气晴朗,空手出门
  4. Tom 收到消息:2022514日天气晴朗,空手出门

分析总结

从上面可以看出,主题中维护了所有的观察者列表,当主题状态改变时,通知所有的观察者。二者的关系松耦合,主题的实现不需要依赖于具体的观察者,当有新的观察者加入时,主题的代码不需要变动。观察者如何处理主题状态的变化也与主题无关(同为雨天,Jack 出门带伞,而 Tom 则淋着雨上班 )。

上面的代码仅仅只是一个观察者模式的示例,在实际开发中,应根据实际情况灵活应对。例如:
在并发情况下,应保证 注册/注销 方法线程安全。
当存在多个主题时,可考虑使用模板类代替主题接口,这样就不用写大量逻辑类似的注册和注销方法。

在 jdk 中,已经提供了观察者模式的实现,他们分别是 java.util.Observer 和 java.util.Observable 。其中,Observable 就是主题的模板类,已经提供了 注册/注销/通知 等核心方法,并且该类已经保证了线程安全。