1. 观察者模式
1.1 什么是观察者模式
观察者模式用于建立一种对象与对象之间的依赖关系,当一个对象发生改变时将自动通知其他对象,其他对象会相应地作出反应。
在观察者模式中有如下角色:
- Subject(抽象主题/被观察者): 抽象主题角色把所有观察者对象保存在一个集合里,每个主题可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject(具体主题/具体被观察者): 该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer(抽象观察者): 观察者的抽象类,定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcreteObserver(具体观察者): 实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,存储具体观察者的有关状态,这些状态需要与具体目标保持一致。
1.2 观察者模式实现
- 观察者
/**
* 抽象观察者
*/
public interface Observer {
// update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
void update();
}
/**
* 具体观察者
*/
public class ConcreteObserverOne implements Observer {
@Override
public void update() {
// 获取消息通知,执行业务代码
System.out.println("ConcreteObserverOne 得到通知!");
}
}
/**
* 具体观察者
*/
public class ConcreteObserverTwo implements Observer {
@Override
public void update() {
// 获取消息通知,执行业务代码
System.out.println("ConcreteObserverTwo 得到通知!");
}
}
- 被观察者
/**
* 抽象目标类
*/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
/**
* 具体目标类
*/
public class ConcreteSubject implements Subject {
// 定义集合,存储所有观察者对象
private ArrayList<Observer> observers = new ArrayList<>();
// 注册方法,向观察者集合中增加一个观察者
@Override
public void attach(Observer observer) {
observers.add(observer);
}
// 注销方法,用于从观察者集合中删除一个观察者
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
// 通知方法
@Override
public void notifyObservers() {
// 遍历观察者集合,调用每一个观察者的响应方法
for (Observer obs : observers) {
obs.update();
}
}
}
- 测试类
public class Client {
public static void main(String[] args) {
// 创建目标类(被观察者)
ConcreteSubject subject = new ConcreteSubject();
// 注册观察者类,可以注册多个
subject.attach(new ConcreteObserverOne());
subject.attach(new ConcreteObserverTwo());
// 具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
subject.notifyObservers();
}
}
2. 发布订阅模式与观察者模式的区别
2.1 定义上的不同
发布订阅模式属于广义上的观察者模式。
- 发布订阅模式是最常用的一种观察者模式的实现,从解耦和重用角度来看,更优于典型的观察者模式。
2.2 两者的区别
我们来看一下观察者模式与发布订阅模式结构上的区别
操作流程上的区别
- 观察者模式:数据源直接通知订阅者发生改变。
- 发布订阅模式:数据源告诉第三方(事件通道)发生了改变,第三方再通知订阅者发生了改变。
3. 观察者模式在实际开发中的应用
3.1 实际开发中的需求场景
在我们日常业务开发中,观察者模式的一个重要作用在于实现业务的解耦。以用户注册的场景为例,假设在用户注册完成时,需要给该用户发送邮件、发送优惠券等操作,如下图所示:
使用观察者模式之后
- UserService 在完成自身的用户注册逻辑之后,仅需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。
- 其它 Service 可以自己订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。
3.2 Spring事件机制
Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:
- 事件
ApplicationEvent
:通过继承它,实现自定义事件。另外,通过它的source
属性可以获取事件源,timestamp
属性可以获得发生时间。 - 事件发布者
ApplicationEventPublisher
:通过它,可以进行事件的发布。 - 事件监听器
ApplicationListener
:通过实现它,进行指定类型的事件的监听。
3.3 代码实现
(1) UserRegisterEvent
- 创建
UserRegisterEvent
事件类,继承ApplicationEvent
类,用户注册事件。代码如下:
/**
* 用户注册事件
*/
public class UserRegisterEvent extends ApplicationEvent {
private String username;
public UserRegisterEvent(Object source) {
super(source);
}
public UserRegisterEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
(2) UserService (事件源+事件发布)
- 创建
UserService
类,代码如下:
/**
* 事件源角色+事件发布
*/
@Service
public class UserService implements ApplicationEventPublisherAware {
private Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String username){
// 执行注册逻辑
logger.info("[register][执行用户{}的注册逻辑]", username);
// 发布用户注册事件
applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
}
}
- 实现
ApplicationEventPublisherAware
接口,从而将ApplicationEventPublisher
注入到其中。 - 在执行完注册逻辑后,调用
ApplicationEventPublisher
的publishEvent(ApplicationEvent event)
方法,发布UserRegisterEvent
事件。
(3) 创建 EmailService
/**
* 事件监听角色
*/
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(UserRegisterEvent event) {
logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
}
}
- 实现
ApplicationListener
接口,通过E
泛型设置感兴趣的事件。 - 实现
onApplicationEvent(E event)
方法,针对监听的UserRegisterEvent
事件,进行自定义处理。
(4) CouponService
@Service
public class CouponService {
private Logger logger = LoggerFactory.getLogger(getClass());
@EventListener
public void addCoupon(UserRegisterEvent event) {
logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
}
}
- 添加
@EventListener
注解,并设置监听的事件为UserRegisterEvent
。
(5) DemoController
- 提供
/demo/register
注册接口
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private UserService userService;
@GetMapping("/register")
public String register(String username) {
userService.register(username);
return "success";
}
}
3.4 代码测试
- 执行
DemoApplication
类,启动项目。 - 调用
http://127.0.0.1:8080/demo/register?username=mashibing
接口,进行注册。IDEA 控制台打印日志如下:
// UserService 发布 UserRegisterEvent 事件
2023-04-19 16:49:40.628 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.service.UserService : [register][执行用户mashibing的注册逻辑]
// EmailService 监听处理该事件
2023-04-19 16:49:40.629 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.EmailService : [onApplicationEvent][给用户(mashibing) 发送邮件]
// CouponService 监听处理该事件
2023-04-19 16:49:40.629 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.CouponService : [addCoupon][给用户(mashibing) 发放优惠劵]
4. 观察者模式总结
1) 观察者模式的优点
- 降低目标类和观察者之间的耦合
- 可以实现广播机制
2) 观察者模式的缺点
- 通知的发送会消耗一定的时间
- 如果被观察者有循环依赖,会导致系统的崩溃
3) 观察者模式常见的使用场景
- 一个对象的改变,需要改变其他对象的时候
- 一个对象的改变,需要进行通知的时候