观察者模式原理及实现
观察者模式(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。不管怎么称呼,只要应用场景符合上面的定义,都可以看作观察者模式。
实际上,观察者模式是一个比较抽象的模式,根据不同的应用场景和需求,有完全不同的实现方式。我们先来看其中最经典的一种实现方式。这也是在讲到这种模式的时候,很多书籍或资料给出的最常见的实现方式。
// 主题接口,对象使用此接口注册为观察者
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}
// 观察者接口,当主题状态改变时它的 update 方法被调用
public interface Observer {
void update(Message message);
}
public class ConcreteSubject implements Subject {
// 每个主题可以有许多观察者
private List<Observer> observers = new ArrayList<Observer>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Message message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class ConcreteObserverOne implements Observer {
@Override
public void update(Message message) {
System.out.println("ConcreteObserverOne is notified.");
}
}
public class ConcreteObserverTwo implements Observer {
@Override
public void update(Message message) {
System.out.println("ConcreteObserverTwo is notified.");
}
}
public class Demo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.registerObserver(new ConcreteObserverOne());
subject.registerObserver(new ConcreteObserverTwo());
subject.notifyObservers(new Message());
}
}
松耦合设计:
主题(Subject)不需要知道观察者(Observer)的具体类是谁、做了些什么或其他任何细节,在任何时候我们都可以增加新的观察者,因为主题唯一依赖的东西是一个实现 Observer 接口的对象列表。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题代码,唯一要做的就是在新的类里实现观察者接口,然后注册成为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。
观察者模式的原理和代码实现都非常简单,下面我们再通过一个具体的例子来重点讲一下,什么情况下需要用到这种设计模式?或者说,这种设计模式能解决什么问题呢?
假设我们在开发一个 P2P 投资理财系统,用户注册成功之后,我们会给用户发放投资体验金。由于逻辑简单,我们直接把这两件事情耦合在一个方法内:
public class UserController {
private UserService userService;
private PromotionService promotionService;
public Long register(String telephone, String password) {
// 注册
long userId = userService.register(telephone, password);
// 发放投资体验金
promotionService.issueNewUserExperienceCash(userId);
return userId;
}
}
但如果需求频繁变动,比如,用户注册成功后,不再发放体验金,而是改为发放优惠券,并且还要给用户发送一封“欢迎注册成功”的短信。这种情况下,我们就需要频繁地修改 register() 函数中的代码,违反开闭原则。而且,如果注册成功之后需要执行的后续操作越来越多,那 register() 函数的逻辑会变得越来越复杂,也就影响到代码的可读性和可维护性。这个时候,观察者模式就能派上用场了。我们利用观察者模式进行下重构:
public interface RegObserver {
// 用户注册成功的回调
void handleRegSuccess(long userId);
}
//发放优惠券
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService;
@Override
public void handleRegSuccess(long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
// 短信通知注册成功
public class RegNotificationObserver implements RegObserver {
private NotificationService notificationService;
@Override
public void handleRegSuccess(long userId) {
notificationService.sendInboxMessage(userId, "Welcome...");
}
}
public class UserController {
private UserService userService;
private List<RegObserver> regObservers = new ArrayList<>();
public Long register(String telephone, String password) {
long userId = userService.register(telephone, password);
// 触发观察者的回调方法
for (RegObserver observer : regObservers) {
observer.handleRegSuccess(userId);
}
return userId;
}
}
当我们需要添加新的观察者时,比如,用户注册成功后,推送用户注册信息给大数据征信系统,基于观察者模式的代码实现,UserController 类的 register() 函数完全不需要修改,只需要再添加一个实现了 RegObserver 接口的类,并且将它注册到 UserController 类中即可。
实际上,设计模式要干的事情就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚松耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。
观察者模式应用场景
观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。
上面讲的例子就是一种同步阻塞的实现方式。观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。如果注册接口对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。具体来讲,当 userService.register() 函数执行完成之后,我们启动一个新的线程来执行观察者的 handleRegSuccess() 函数,这样主线程就可以直接返回结果给客户端了。
上面这两个场景,不管是同步阻塞实现方式还是异步非阻塞实现方式,都是进程内的实现方式。如果用户注册成功后,我们需要发送用户信息给大数据征信系统,而大数据征信系统是一个独立的系统,跟它之间的交互是跨不同进程的,那如何实现一个跨进程的观察者模式呢?
常用的一种实现方式就是基于消息队列(Message Queue)来实现。当然,这种实现方式也有弊端,那就是需要引入一个新的系统,增加了维护成本。不过好处也非常明显,基于消息队列的实现方式,被观察者和观察者解耦更加彻底,两部分的耦合更小。被观察者完全不感知观察者,同理,观察者也完全不感知被观察者。被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。
观察者模式使用案例
1. Spring Event
Spring 中实现的观察者模式包含三部分:Event 事件(相当于消息)、Listener 监听者(相当于观察者)以及Publisher 发送者(相当于被观察者)。下面,通过一个例子来看下 Spring 提供的观察者模式如何使用:
// Event事件
public class DemoEvent extends ApplicationEvent {
private String message;
public DemoEvent(Object source, String message) {
super(source);
}
public String getMessage() {
return this.message;
}
}
// Listener监听者
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent demoEvent) {
String message = demoEvent.getMessage();
System.out.println(message);
}
}
// Publisher发送者
@Component
public class DemoPublisher {
@Autowired
private ApplicationContext applicationContext;
public void publishEvent(DemoEvent demoEvent) {
this.applicationContext.publishEvent(demoEvent);
}
}
其中,ApplicationEvent 和 ApplicationListener 的代码实现都非常简单,内部并不包含太多属性和方法。实际上,它们最大的作用是做类型标识之用。
在上面讲观察者模式的时候,我们提到,观察者需要事先注册到被观察者中。那在 Spring 的实现中,观察者注册到了哪里呢?又是如何注册的呢?实际上,我们把观察者注册到了 ApplicationContext 对象中。这里的 ApplicationContext 就相当于 Google EventBus 框架中的“事件总线”。具体到源码上,ApplicationContext 只是一个接口,具体的代码实现包含在它的实现类 AbstractApplicationContext 中。其中,发送事件和注册监听者相关的代码部分如下:
public abstract class AbstractApplicationContext extends ... {
private final Set<ApplicationListener<?>> applicationListeners;
// 发布事件
public void publishEvent(ApplicationEvent event) {
this.publishEvent(event, (ResolvableType)null);
}
// 注册监听器
protected void registerListeners() {
Iterator var1 = this.getApplicationListeners().iterator();
while(var1.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var1.next();
this.getApplicationEventMulticaster().addApplicationListener(listener);
}
// ...
}
}
从上面的代码中,我们发现,真正的消息发送,实际上是通过 ApplicationEventMulticaster 这个类来完成的。它通过线程池,支持异步非阻塞、同步阻塞这两种类型的观察者模式。借助 Spring 提供的观察者模式框架,如果我们要在 Spring 下实现某个事件的发送和监听,只需要做很少的工作,定义事件、定义监听器、往 ApplicationContext 中发送事件就可以了,剩下的工作都由 Spring 框架来帮我们完成。