引言
在软件开发中,我们经常面临需要在多个对象之间进行通信的挑战。特别是当一个对象的状态发生变化时,我们希望所有依赖于这个状态的对象都能自动更新。这就是观察者设计模式大显身手的地方。
简介
观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,使得当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。
核心组件
- Subject(主题):也称为
Observable
,它维护一组观察者,提供添加、删除和通知观察者的接口; - Observer(观察者):为所有具体观察者定义一个接口,在得到主题的通知时更新自己;
- ConcreteSubject(具体主题):保存状态,当状态变化时通知观察者;
- ConcreteObserver(具体观察者):实现观察者更新接口,以便在主题状态变化时更新自己。
经典实现
// 主题-被观察者
public interface Subject {
void registerObserver(Observer observer); //注册观察者
void removeObserver(Observer observer); //移除观察者
void notifyObservers(Message message); //通知观察者
}
// 观察者
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);
}
}
}
//观察者1
public class ConcreteObserverOne implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
System.out.println("ConcreteObserverOne is notified.");
}
}
//观察者2
public class ConcreteObserverTwo implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
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());
}
}
投资理财系统
比如,我们现在在开发一个投资理财系统,用户注册成功后,会给用户发放投资体验金,代码实现大致如下:
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);
}
//观察者1
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService; // 依赖注入
@Override
public void handleRegSuccess(long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
//观察者2
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 void setRegObservers(List<RegObserver> observers) {
regObservers.addAll(observers);
}
public Long register(String telephone, String password) {
long userId = userService.register(telephone, password);
//通知观察者
for (RegObserver observer : regObservers) {
observer.handleRegSuccess(userId);
}
return userId;
}
}
被观察者代码和观察者代码在同一个线程中执行,被观察者代码一直阻塞,直到所有的观察者代码都执行完毕后,才执行后续的代码。
即register
函数依次调用执行每个观察者的handleRegSuccess
函数,都执行完成后,才会返回结果给客户端。
异步非阻塞实现
如果注册接口是一个调用非常频繁的接口,对性能非常敏感,希望接口的响应时间尽可能的短,可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。
当register
函数执行完成后,启动一个新的线程来执行观察者的handleRegSuccess
函数。
跨进程实现
同步阻塞和异步非阻塞都是进程内的实现方式。
而基于消息队列实现的方式则属于一个跨进程的实现方式,观察者和被观察者解耦的更加彻底,两者都感知不到对方的存在。被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。
总结
观察者模式提供了一种强大的方法来实现对象之间的松耦合通信。它允许系统在不修改现有代码的情况下,通过增加新的观察者来扩展其功能。通过使用观察者模式,我们可以构建出更加灵活、可维护和响应式的软件系统。
参考文献
《极客时间-设计模式之美》