观察者模式(上):理解观察者模式
本文内容
前言
前面我们学习完了创建型和结构型的设计模式,从这篇文章开始,将学习最后一个类型的设计模式—行为型。
创建型主要解决的是 “对象的创建” 问题,结构型主要解决的是 “类或对象的组合组装” 问题,而行为型主要解决的是 “类或对象之间的交互” 问题。
行为型设计模式比较多,有:观察者模式、模版模式、策略模式、责任链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
观察者模式在实际开发中也比较常用,根据应用场景的不同有着不同的实现方式,常见的有如下几种:
- 同步阻塞的实现;
- 异步非阻塞的实现;
- 进程内的实现;
- 跨进程的实现。
这篇文章首先介绍观察者模式的原理、应用场景,以及它的几种不同实现方式。下一篇文章将基于观察者模式实现一个异步非阻塞的 EventBus,将观察者模式运用到实际场景中。
1. 观察者模式原理
观察者模式(Observer Design Pattern)也叫 发布订阅模式(Publish-Subscribe Design Pattern),官方定义如下:
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、EventEmitter-EventListener、Dispatcher-Listener 等。
观察者模式有多种实现方式,一种典型的实现方式如下:
// 观察者
interface Observer {
// 观察者—更新 Message 操作
void update(Message message);
}
// 被观察者
interface Subject {
// 注册/移除观察者
void registerObserver(Observer observer);
void removeObserver(Observer observer);
// 通知所有的观察者
void notifyObservers(Message message);
}
// 具体的被观察者
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Message message) {
// 通知所有的观察者 update message
for (Observer observer : observers) {
observer.update(message);
}
}
}
// 具体的观察者 01
class ConcreteObserver01 implements Observer {
@Override
public void update(Message message) {
// TODO: 获取消息通知,执行 ConcreteObserver01 自己的逻辑
System.out.println("ConcreteObserver01 is notified.");
}
}
// 具体的观察者 02
class ConcreteObserver02 implements Observer {
@Override
public void update(Message message) {
// TODO: 获取消息通知,执行 ConcreteObserver02 自己的逻辑
System.out.println("ConcreteObserver02 is notified.");
}
}
public class ClassicObserver {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.registerObserver(new ConcreteObserver01());
subject.registerObserver(new ConcreteObserver02());
// TODO: 发生某种事情,通知所有的观察者
subject.notifyObservers(new Message());
}
}
class Message {
}
解析:
Subject 是被观察者,定义一组注册/移除/通知观察者的方法,由具体的被观察者 ConcreteSubject 去实现。
Observer 是观察者,定义一组动作,所有具体的观察者(ConcreteObserver01 和 ConcreteObserver02)都继承自 Observer,实现这组动作。
在 Observer 收到 Subject 状态变化的通知时(
notifyObservers()
被调用),调用 Observer 具体实现类的这组动作,因为 Observer 的实现类各不相同,所有会做出各自不同的动作。
这是观察者模式的 “模板代码”,只能反映出大致的设计思路。在实际应用时,都是比较灵活的,不必一五一十地照着实现。
2. 应用场景
观察者模式原理非常简单,重点来看看具体的应用场景,在什么情况下需要使用观察者模式,观察者模式能解决什么问题?
假设一个投资理财系统在用户注册后会发放投资体验金,那么这个系统的 UserController 大致实行如下:
public class UserController {
private UserService userService; // 依赖注入
private PromotionService promotionService; // 依赖注入
public Long register(String telephone, String password) {
// 省略输入参数的校验代码
// 省略 userService.register() 异常的 try-catch 代码
long userId = userService.register(telephone, password);
promotionService.issueNewUserExperienceCash(userId); // 发放体验金
return userId;
}
}
这里虽然 注册接口做了两件事—注册和发放体验金,违反了单一职责原则,但是如果 后续没有扩展和修改的需求,这样的实现是可以接受的。如果 非要使用观察者模式,就意味着要引入更多的类和代码,反而是一种 过度设计。
相反,如果 需求频繁变动,比如用户注册后,又要改为发放优惠卷、还要给用户发送一封 “欢迎注册” 的站内信。此时如果还用上面的设计,就需要 频繁改动 register()
中的代码,违反了开闭原则。而且将多个功能写在同一个接口下会 增加该接口的复杂度,影响代码的可读性和可维护性。
此时,观察者模式就派上用场了。我们利用观察者模式进行重构:
// 监听器(观察者)
interface RegisterListener {
void handleRegisterSuccess(long userId);
}
class RegisterPromotionListener implements RegisterListener {
private PromotionService promotionService;
@Override
public void handleRegisterSuccess(long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
class RegisterNotificationListener implements RegisterListener {
private NotificationService notificationService;
@Override
public void handleRegisterSuccess(long userId) {
notificationService.sendInboxMessage(userId, "Welcome...");
}
}
// 触发器(被监听者)
class RegisterDispatcher {
private List<RegisterListener> listeners = new ArrayList<>();
public void addListener(RegisterListener listener) {
listeners.add(listener);
}
public void removeListener(RegisterListener listener) {
listeners.remove(listener);
}
public void notifyListeners(long userId) {
for (RegisterListener listener : listeners) {
listener.handleRegisterSuccess(userId);
}
}
}
class UserController {
private UserService userService;
private RegisterDispatcher registerDispatcher;
public Long register(String telephone, String password) {
//省略输入参数的校验代码
// 省略 userService.register() 异常的 try-catch 代码
long userId = userService.register(telephone, password);
// 添加需要的监听器
addListenerOfRegister();
// 通知所有监听器
registerDispatcher.notifyListeners(userId);
return userId;
}
// 添加 Register 相关的 Listener,实际场景可以把 List 作为参数,由外部调用者决定要注入什么 Listener
private void addListenerOfRegister() {
registerDispatcher.addListener(new RegisterPromotionListener());
registerDispatcher.addListener(new RegisterNotificationListener());
}
}
当需要添加监听器时,现在就只需要添加一个实现了 RegisterListener 的类,然后在 addListenerOfRegister()
方法中添加该类即可。
其实设计模式主要作用就是解耦:
- 创建型将对象的创建和使用解耦;
- 结构型将不同行为(功能代码)解耦;
- 观察者模式将观察者和被观察者的代码解耦。
3. 观察者模式的不同实现方式
观察者模式的应用非常广泛,比如我们常用的订阅系统,如邮件订阅、RSS Feeds 等,本质都是观察者模式。
不同的应用场景下,观察者模式有着不同的实现,在文章开头也说过,主要有以下几种实现方式:
- 同步阻塞的实现;
- 异步非阻塞的实现;
- 进程内的实现;
- 跨进程的实现。
同步阻塞 的实现方式,观察者和被观察者代码在同一个线程内执行,被观察者要一直阻塞,直到所有观察者的方法都执行完。
上面投资理财的例子,就是一种 同步阻塞 的实现方式,
register()
中需要等每个观察者的handleRegSuccess()
函数都执行完成之后,才会返回结果给客户端。
了解了同步阻塞后,异步非阻塞 的实现方式就很简单了,只需要 启动一个新的线程来执行观察者的方法 即可。
上面的两种实现方式,其实都属于进程内的实现,那如何实现一个 跨进程的观察者模式 呢?在微服务架构中,常见的做法是使用 RPC 接口 来进行处理,不过一般我们会使用更优雅的实现方式—MQ。基于 MQ,被观察者和观察者解耦将更加彻底,被观察者和观察者两两都是无感知的。被观察者只管发送消息到 MQ,观察者只管从 MQ 中消费消息(执行对应的逻辑)。
观察者模式和生产者-消费者模型的联系和区别:
联系:
- 生产者-消费者模型可以算观察者模式的一种异步非阻塞的应用。
区别:
- 在观察者模式中,被观察者知道观察者的存在,要主动调用方法通知它;而在生产者-消费者模型中,由于加入了中间层队列,所以生产者和消费者完全对对方无感知,实现了完全解耦;
- 在观察者模式中,被观察者发送了通知,其他观察者都会对此通知进行处理;而在生产者-消费者模型中,一条消息一般只会有一个消费者消费。