观察者模式原理及实现

观察者模式(Observer Design Pattern)也被称为发布订阅模式,在 GoF《设计模式》中的定义为:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、Dispatcher-Listener。不管怎么称呼,只要应用场景符合上面的定义,都可以看作观察者模式。

实际上,观察者模式是一个比较抽象的模式,根据不同的应用场景和需求,有完全不同的实现方式。我们先来看其中最经典的一种实现方式。这也是在讲到这种模式的时候,很多书籍或资料给出的最常见的实现方式。

  1. // 主题接口,对象使用此接口注册为观察者
  2. public interface Subject {
  3. void registerObserver(Observer observer);
  4. void removeObserver(Observer observer);
  5. void notifyObservers(Message message);
  6. }
  7. // 观察者接口,当主题状态改变时它的 update 方法被调用
  8. public interface Observer {
  9. void update(Message message);
  10. }
  11. public class ConcreteSubject implements Subject {
  12. // 每个主题可以有许多观察者
  13. private List<Observer> observers = new ArrayList<Observer>();
  14. @Override
  15. public void registerObserver(Observer observer) {
  16. observers.add(observer);
  17. }
  18. @Override
  19. public void removeObserver(Observer observer) {
  20. observers.remove(observer);
  21. }
  22. @Override
  23. public void notifyObservers(Message message) {
  24. for (Observer observer : observers) {
  25. observer.update(message);
  26. }
  27. }
  28. }
  29. public class ConcreteObserverOne implements Observer {
  30. @Override
  31. public void update(Message message) {
  32. System.out.println("ConcreteObserverOne is notified.");
  33. }
  34. }
  35. public class ConcreteObserverTwo implements Observer {
  36. @Override
  37. public void update(Message message) {
  38. System.out.println("ConcreteObserverTwo is notified.");
  39. }
  40. }
  41. public class Demo {
  42. public static void main(String[] args) {
  43. ConcreteSubject subject = new ConcreteSubject();
  44. subject.registerObserver(new ConcreteObserverOne());
  45. subject.registerObserver(new ConcreteObserverTwo());
  46. subject.notifyObservers(new Message());
  47. }
  48. }

松耦合设计:
主题(Subject)不需要知道观察者(Observer)的具体类是谁、做了些什么或其他任何细节,在任何时候我们都可以增加新的观察者,因为主题唯一依赖的东西是一个实现 Observer 接口的对象列表。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题代码,唯一要做的就是在新的类里实现观察者接口,然后注册成为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。

观察者模式的原理和代码实现都非常简单,下面我们再通过一个具体的例子来重点讲一下,什么情况下需要用到这种设计模式?或者说,这种设计模式能解决什么问题呢?

假设我们在开发一个 P2P 投资理财系统,用户注册成功之后,我们会给用户发放投资体验金。由于逻辑简单,我们直接把这两件事情耦合在一个方法内:

  1. public class UserController {
  2. private UserService userService;
  3. private PromotionService promotionService;
  4. public Long register(String telephone, String password) {
  5. // 注册
  6. long userId = userService.register(telephone, password);
  7. // 发放投资体验金
  8. promotionService.issueNewUserExperienceCash(userId);
  9. return userId;
  10. }
  11. }

但如果需求频繁变动,比如,用户注册成功后,不再发放体验金,而是改为发放优惠券,并且还要给用户发送一封“欢迎注册成功”的短信。这种情况下,我们就需要频繁地修改 register() 函数中的代码,违反开闭原则。而且,如果注册成功之后需要执行的后续操作越来越多,那 register() 函数的逻辑会变得越来越复杂,也就影响到代码的可读性和可维护性。这个时候,观察者模式就能派上用场了。我们利用观察者模式进行下重构:

  1. public interface RegObserver {
  2. // 用户注册成功的回调
  3. void handleRegSuccess(long userId);
  4. }
  5. //发放优惠券
  6. public class RegPromotionObserver implements RegObserver {
  7. private PromotionService promotionService;
  8. @Override
  9. public void handleRegSuccess(long userId) {
  10. promotionService.issueNewUserExperienceCash(userId);
  11. }
  12. }
  13. // 短信通知注册成功
  14. public class RegNotificationObserver implements RegObserver {
  15. private NotificationService notificationService;
  16. @Override
  17. public void handleRegSuccess(long userId) {
  18. notificationService.sendInboxMessage(userId, "Welcome...");
  19. }
  20. }
  21. public class UserController {
  22. private UserService userService;
  23. private List<RegObserver> regObservers = new ArrayList<>();
  24. public Long register(String telephone, String password) {
  25. long userId = userService.register(telephone, password);
  26. // 触发观察者的回调方法
  27. for (RegObserver observer : regObservers) {
  28. observer.handleRegSuccess(userId);
  29. }
  30. return userId;
  31. }
  32. }

当我们需要添加新的观察者时,比如,用户注册成功后,推送用户注册信息给大数据征信系统,基于观察者模式的代码实现,UserController 类的 register() 函数完全不需要修改,只需要再添加一个实现了 RegObserver 接口的类,并且将它注册到 UserController 类中即可。

实际上,设计模式要干的事情就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚松耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。

观察者模式应用场景

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。

上面讲的例子就是一种同步阻塞的实现方式。观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。如果注册接口对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。具体来讲,当 userService.register() 函数执行完成之后,我们启动一个新的线程来执行观察者的 handleRegSuccess() 函数,这样主线程就可以直接返回结果给客户端了。

上面这两个场景,不管是同步阻塞实现方式还是异步非阻塞实现方式,都是进程内的实现方式。如果用户注册成功后,我们需要发送用户信息给大数据征信系统,而大数据征信系统是一个独立的系统,跟它之间的交互是跨不同进程的,那如何实现一个跨进程的观察者模式呢?

常用的一种实现方式就是基于消息队列(Message Queue)来实现。当然,这种实现方式也有弊端,那就是需要引入一个新的系统,增加了维护成本。不过好处也非常明显,基于消息队列的实现方式,被观察者和观察者解耦更加彻底,两部分的耦合更小。被观察者完全不感知观察者,同理,观察者也完全不感知被观察者。被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。

观察者模式使用案例

1. Spring Event

Spring 中实现的观察者模式包含三部分:Event 事件(相当于消息)、Listener 监听者(相当于观察者)以及Publisher 发送者(相当于被观察者)。下面,通过一个例子来看下 Spring 提供的观察者模式如何使用:

  1. // Event事件
  2. public class DemoEvent extends ApplicationEvent {
  3. private String message;
  4. public DemoEvent(Object source, String message) {
  5. super(source);
  6. }
  7. public String getMessage() {
  8. return this.message;
  9. }
  10. }
  11. // Listener监听者
  12. @Component
  13. public class DemoListener implements ApplicationListener<DemoEvent> {
  14. @Override
  15. public void onApplicationEvent(DemoEvent demoEvent) {
  16. String message = demoEvent.getMessage();
  17. System.out.println(message);
  18. }
  19. }
  20. // Publisher发送者
  21. @Component
  22. public class DemoPublisher {
  23. @Autowired
  24. private ApplicationContext applicationContext;
  25. public void publishEvent(DemoEvent demoEvent) {
  26. this.applicationContext.publishEvent(demoEvent);
  27. }
  28. }

其中,ApplicationEvent 和 ApplicationListener 的代码实现都非常简单,内部并不包含太多属性和方法。实际上,它们最大的作用是做类型标识之用。

在上面讲观察者模式的时候,我们提到,观察者需要事先注册到被观察者中。那在 Spring 的实现中,观察者注册到了哪里呢?又是如何注册的呢?实际上,我们把观察者注册到了 ApplicationContext 对象中。这里的 ApplicationContext 就相当于 Google EventBus 框架中的“事件总线”。具体到源码上,ApplicationContext 只是一个接口,具体的代码实现包含在它的实现类 AbstractApplicationContext 中。其中,发送事件和注册监听者相关的代码部分如下:

  1. public abstract class AbstractApplicationContext extends ... {
  2. private final Set<ApplicationListener<?>> applicationListeners;
  3. // 发布事件
  4. public void publishEvent(ApplicationEvent event) {
  5. this.publishEvent(event, (ResolvableType)null);
  6. }
  7. // 注册监听器
  8. protected void registerListeners() {
  9. Iterator var1 = this.getApplicationListeners().iterator();
  10. while(var1.hasNext()) {
  11. ApplicationListener<?> listener = (ApplicationListener)var1.next();
  12. this.getApplicationEventMulticaster().addApplicationListener(listener);
  13. }
  14. // ...
  15. }
  16. }

从上面的代码中,我们发现,真正的消息发送,实际上是通过 ApplicationEventMulticaster 这个类来完成的。它通过线程池,支持异步非阻塞、同步阻塞这两种类型的观察者模式。借助 Spring 提供的观察者模式框架,如果我们要在 Spring 下实现某个事件的发送和监听,只需要做很少的工作,定义事件、定义监听器、往 ApplicationContext 中发送事件就可以了,剩下的工作都由 Spring 框架来帮我们完成。