每天熟悉一个计划模式-观察者模式:事件驱动架构的基石 ...

打印 上一主题 下一主题

主题 1544|帖子 1544|积分 4632

一、媒介

在分布式系统开辟中,我们经常会遇到这样的场景:订单服务完成付出后须要通知库存服务扣减库存,用户状态变动时须要同步更新多个关接洽统的缓存,甚至是 GUI 界面上按钮状态须要随数据模子动态变化。传统的硬编码调用方式会导致模块间耦合度飙升,牵一发而动全身的修改成本让开辟者苦不堪言。观察者模式作为事件驱动架构的焦点基石,通过建立对象间的动态依赖关系,让 “数据变化通知” 这一通用需求有了优雅的办理方案。下面让咱们一起来好好了解下~
二、观察者模式原型计划及阐明

观察者模式(Observer Pattern)是一种行为型计划模式,作为计划模式家族中的关键成员,在实现对象间解耦这一关键目标上发挥着无可替代的战略作用。它奥妙地界说了对象之间一种松耦合的一对多依赖关系,当被观察对象(Subject)的状态发生变化时,全部依赖于它的观察对象(Observer)都会自动收到通知,并能够根据自身需求举行相应的更新操作 。​
3.1UML 类图介绍

下面我们结合类图一起来研究一下这个模式的计划方式:

这里我们可以看到在观察者模式中,最主要的就是上面四个焦点角色:
Subject(目标):它是一个抽象类或接口,界说了一系列方法,用于管理观察者对象的注册和移除,以及在自身状态发生变化时通知观察者。它是观察者模式的焦点,维护着观察者列表,并不关心具体的观察者是谁以及它们如何处理通知,只负责将通知通报给全部注册的观察者,起到了抽象和规范的作用 。​
Observer(观察者):同样是一个抽象类或接口,界说了一个更新方法update。当目标对象的状态发生改变并发出通知时,全部注册的观察者的update方法会被调用,观察者通过这个方法来获取目标的状态并执行相应的操作,但它不了解目标的具体实现细节,只关注自身的更新逻辑 。​
ConcreteSubject(具体目标):是Subject的具体实现类,包含了实际的业务逻辑和状态。它负责维护观察者列表,实现添加、移除观察者以及通知观察者的方法。当它的状态发生变化时,会调用notifyObservers方法,通知全部注册的观察者,并且可以根据须要提供获取和设置自身状态的方法 。​
ConcreteObserver(具体观察者):实现了Observer接口,拥有自己的状态和行为。在update方法中,它会根据具体目标的状态变化,执行特定的操作,这些操作通常与自身的业务需求相关。它与具体目标之间存在关联,通过update方法接收具体目标的通知并举行处理 。
这四个角色紧密协作,Subject和Observer界说了抽象的接口规范,ConcreteSubject和ConcreteObserver则基于这些规范实现具体的业务逻辑,它们之间的松耦合关系使得系统具有精良的扩展性和维护性。
3.2观察者模式中数据通报的战略剖析:推模子与拉模子

观察者模式在实际应用中,当目标对象(Subject)状态改变时,须要将状态信息通报给观察者(Observer),此时就产生了两种不同的信息通报方式,即推模子和拉模子。这两种模子是为了满足不同场景下数据通报的需求而计划的,它们在数据的发送和接收方式上有所不同,从而衍生出了不同的使用特性和实用场景。
推模子(Push Model)

这种方式是当目标对象的状态发生变化时,目标对象会主动将完整的状态数据推送给全部已注册的观察者。也就是说,目标对象不仅通知观察者状态发生了变化,还直接把相关的状态数据一同发送已往,观察者无需主动去获取数据,被动接收即可。
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. // 观察者接口
  4. interface Observer {
  5.     void update(String newState);
  6. }
  7. // 目标对象接口
  8. interface Subject {
  9.     void attach(Observer observer);
  10.     void detach(Observer observer);
  11.     void notifyObservers(String newState);
  12. }
  13. // 具体目标对象
  14. class ConcreteSubject implements Subject {
  15.     private List<Observer> observers = new ArrayList<>();
  16.     private String state;
  17.     @Override
  18.     public void attach(Observer observer) {
  19.         observers.add(observer);
  20.     }
  21.     @Override
  22.     public void detach(Observer observer) {
  23.         observers.remove(observer);
  24.     }
  25.     @Override
  26.     public void notifyObservers(String newState) {
  27.         this.state = newState;
  28.         for (Observer o : observers) {
  29.             o.update(newState); // 直接将新状态数据推送给观察者
  30.         }
  31.     }
  32.     public String getState() {
  33.         return state;
  34.     }
  35. }
  36. // 具体观察者
  37. class ConcreteObserver implements Observer {
  38.     private String name;
  39.     public ConcreteObserver(String name) {
  40.         this.name = name;
  41.     }
  42.     @Override
  43.     public void update(String newState) {
  44.         System.out.println(name + " 接收到新状态: " + newState);
  45.     }
  46. }
复制代码
  1. #  测试
  2. public class Main {
  3.     public static void main(String[] args) {
  4.         ConcreteSubject subject = new ConcreteSubject();
  5.         ConcreteObserver observer1 = new ConcreteObserver("Observer1");
  6.         ConcreteObserver observer2 = new ConcreteObserver("Observer2");
  7.         subject.attach(observer1);
  8.         subject.attach(observer2);
  9.         subject.notifyObservers("状态已变更为新状态");
  10.     }
  11. }
复制代码
整个推模子的工作流程如下:

  • 创建 ConcreteSubject 目标对象和多个 ConcreteObserver 观察者对象。
  • 调用 ConcreteSubject 的 attach 方法将观察者注册到目标对象中。
  • 当目标对象的状态发生变化时,调用 ConcreteSubject 的 notifyObservers 方法,并传入新状态数据。
  • notifyObservers 方法会遍历全部已注册的观察者,调用它们的 update 方法,将新状态数据推送给观察者。
  • 观察者的 update 方法接收到数据后举行相应的处理。
 这种方式的长处在于:

  • 使用简单:对于观察者而言,实现起来较为轻松。它只须要实现一个接收数据的方法(如update方法),等待目标对象推送数据过来并举行处理就行,不须要额外去考虑如何获取数据。
  • 实时性高:特别适合那些对实时性要求较高且数据量相对较小的场景。因为目标对象主动推送,所以观察者能够敏捷得到最新的状态信息,及时做出响应。
而相对应的也会存在如下问题


  • 大概推送无关数据:目标对象在推送数据时,大概会包含一些观察者并不关心的信息。比方,目标对象的状态数据包含了多个属性,但某个观察者只关注其中一个属性的变化,此时其他无关属性的数据也会被推送给该观察者,造成了不须要的数据传输和资源浪费。
  • 增加网络传输压力:在分布式系统中,如果目标对象有很多已注册的观察者,并且频繁地推送大量数据,那么网络传输的负担会显著增加,大概影响整个系统的性能。
用一个气候信息系统来说,气候数据采会集心就可以作为目标对象,而各个用户终端则代表观察者。当气候数据(如温度、湿度、风力等)发生变化时,采会集心主动将全部的气候数据推送给各个用户终端。用户终端只须要接收这些数据并举行展示即可,无需自己去获取。但大概存在的问题是,有些用户大概只关心温度信息,而采会集心推送的包含湿度、风力等其他数据对这些用户来说是多余的。 
拉模子(Pull Model) 

这种方式是在目标对象在状态发生变化时,仅仅是通知观察者事件已经发生了,而不会主动推送具体的状态数据。观察者在接收到通知后,须要主动调用目标对象提供的接口(如getState方法)来获取所需的状态数据。
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. // 观察者接口
  4. interface Observer {
  5.     // 当目标对象状态改变时,此方法会被调用,用于更新观察者状态
  6.     void update();
  7. }
  8. // 目标对象接口
  9. interface Subject {
  10.     // 注册观察者
  11.     void attach(Observer observer);
  12.     // 移除观察者
  13.     void detach(Observer observer);
  14.     // 通知所有观察者状态发生了改变
  15.     void notifyObservers();
  16.     // 提供获取当前状态的方法,供观察者主动拉取
  17.     String getState();
  18. }
  19. // 具体目标对象
  20. class ConcreteSubject implements Subject {
  21.     private List<Observer> observers = new ArrayList<>();
  22.     private String state;
  23.     @Override
  24.     public void attach(Observer observer) {
  25.         observers.add(observer);
  26.     }
  27.     @Override
  28.     public void detach(Observer observer) {
  29.         observers.remove(observer);
  30.     }
  31.     @Override
  32.     public void notifyObservers() {
  33.         // 遍历所有注册的观察者,调用其 update 方法通知状态改变
  34.         for (Observer observer : observers) {
  35.             observer.update();
  36.         }
  37.     }
  38.     @Override
  39.     public String getState() {
  40.         return state;
  41.     }
  42.     public void setState(String state) {
  43.         this.state = state;
  44.         // 当状态设置完成后,通知所有观察者
  45.         notifyObservers();
  46.     }
  47. }
  48. // 具体观察者
  49. class ConcreteObserver implements Observer {
  50.     private String name;
  51.     private Subject subject;
  52.     public ConcreteObserver(String name, Subject subject) {
  53.         this.name = name;
  54.         this.subject = subject;
  55.     }
  56.     @Override
  57.     public void update() {
  58.         // 主动从目标对象获取最新状态
  59.         String newState = subject.getState();
  60.         System.out.println(name + " 接收到新状态: " + newState);
  61.     }
  62. }
  63. public class Main {
  64.     public static void main(String[] args) {
  65.         // 创建具体目标对象
  66.         ConcreteSubject subject = new ConcreteSubject();
  67.         // 创建具体观察者对象,并关联目标对象
  68.         ConcreteObserver observer1 = new ConcreteObserver("Observer1", subject);
  69.         ConcreteObserver observer2 = new ConcreteObserver("Observer2", subject);
  70.         // 注册观察者到目标对象
  71.         subject.attach(observer1);
  72.         subject.attach(observer2);
  73.         // 改变目标对象的状态
  74.         subject.setState("New State");
  75.     }
  76. }   
复制代码
 拉模子的长处


  • 按需获取数据:观察者可以根据自身的实际需求,有选择性地去获取目标对象的状态数据。它可以只获取自己关心的那部分数据,避免了接收无关数据,提高了数据获取的针对性和效率。
  • 淘汰资源浪费:由于观察者只获取自己须要的数据,所以不会产生不须要的数据传输,节流了系统资源,尤其是在数据量较大的环境下上风更为显着。
拉模子的缺点


  • 增加交互复杂度:这种模子须要在目标对象和观察者之间明白界说数据获取的接口,并且观察者须要主动去调用这些接口获取数据。这增加了两者之间的交互逻辑和实现的复杂度。
  • 大概存在数据获取不及时:如果观察者没有及时响应目标对象的通知并调用接口获取数据,那么它获取到的数据大概就不是最新的状态。比如在一些对实时性要求极高的场景中,这种延迟大概会导致数据的不准确或不及时,影响系统的正常运行。
这里我们照旧以气候信息系统为例区别于推模子,当气候数据采会集心检测到气候状态变化后,只通知各个用户终端有数据更新了。用户终端如果想要获取最新的气候信息,就须要主动调用采会集心提供的接口来获取。比如有些用户只关心风力信息,那么它就可以只获取风力相关的数据,而不消接收其他无关的气候数据。但如果用户终端没有及时去调用接口获取数据,就大概获取到的是旧的风力信息。
三、观察者模式在事件驱动的场景或框架应用

1. 跨模块状态同步的典范场景

在当代的微服务架构中,拿电商系统来说,订单模块和库存模块是两个紧密关联但又须要保持相对独立的焦点模块 。在传统的紧密耦合计划中,当订单状态发生变化,如订单创建、付出乐成、取消订单等操作时,订单模块须要直接调用库存模块的方法来更新库存数量,这就导致订单模块和库存模块之间的依赖关系非常紧密。如果库存模块的接口发生变化,订单模块也须要相应地修改代码,维护成本较高。我们来看一下传统紧密耦合计划:
  1. // 库存模块类
  2. class InventoryModule {
  3.     // 更新库存数量的方法
  4.     public void updateInventory(int quantity) {
  5.         // 实际更新库存逻辑代码,比如连接数据库更新库存数量等
  6.         System.out.println("库存数量更新为:" + quantity);
  7.     }
  8. }
  9. // 订单模块类
  10. class OrderModule {
  11.     private InventoryModule inventoryModule;
  12.     public OrderModule(InventoryModule inventoryModule) {
  13.         this.inventoryModule = inventoryModule;
  14.     }
  15.     // 模拟订单支付成功操作
  16.     public void orderPaid(int quantity) {
  17.         // 订单支付成功,直接调用库存模块更新库存方法
  18.         inventoryModule.updateInventory(quantity);
  19.     }
  20. }
复制代码
这里OrderModule类中直接依赖InventoryModule类,orderPaid方法中直接调用InventoryModule的updateInventory方法,当InventoryModule接口变化时,OrderModule必须修改代码。 
引入观察者模式后,订单模块可以作为被观察对象(Subject),库存模块作为观察对象(Observer) 。当订单状态发生变化时,订单模块不再直接调用库存模块的方法,而是通知全部注册的观察者(包罗库存模块)。库存模块接收到通知后,根据订单的最新状态举行相应的库存更新操作。
这样,订单模块和库存模块之间实现了解耦,两边可以独立地举行功能扩展和修改,互不影响。比方,当须要增加新的库存调整逻辑时,只需在库存模块中举行修改,订单模块无需感知;当订单模块举行业务逻辑优化时,也不会影响到库存模块的正常运行,极大地提高了系统的灵活性和可维护性 。 
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. // 抽象观察者接口
  4. interface Observer {
  5.     void update();
  6. }
  7. // 抽象被观察对象类
  8. abstract class Subject {
  9.     private List<Observer> observers = new ArrayList<>();
  10.     public void attach(Observer observer) {
  11.         observers.add(observer);
  12.     }
  13.     public void detach(Observer observer) {
  14.         observers.remove(observer);
  15.     }
  16.     public void notifyObservers() {
  17.         for (Observer observer : observers) {
  18.             observer.update();
  19.         }
  20.     }
  21. }
  22. // 订单模块类,作为被观察对象
  23. class OrderModuleSubject extends Subject {
  24.     // 模拟订单状态
  25.     private String orderStatus;
  26.     public String getOrderStatus() {
  27.         return orderStatus;
  28.     }
  29.     public void setOrderStatus(String orderStatus) {
  30.         this.orderStatus = orderStatus;
  31.         // 订单状态变化,通知观察者
  32.         notifyObservers();
  33.     }
  34. }
  35. // 库存模块类,作为观察者
  36. class InventoryModuleObserver implements Observer {
  37.     private OrderModuleSubject orderModule;
  38.     public InventoryModuleObserver(OrderModuleSubject orderModule) {
  39.         this.orderModule = orderModule;
  40.         orderModule.attach(this);
  41.     }
  42.     @Override
  43.     public void update() {
  44.         // 根据订单状态进行库存更新操作
  45.         String status = orderModule.getOrderStatus();
  46.         if ("已支付".equals(status)) {
  47.             // 实际更新库存逻辑代码,比如连接数据库更新库存数量等
  48.             System.out.println("因为订单已支付,库存更新操作执行");
  49.         }
  50.     }
  51. }
复制代码
这样OrderModuleSubject作为被观察对象,InventoryModuleObserver作为观察者,OrderModuleSubject状态改变时通知InventoryModuleObserver,两者之间解耦,各自修改扩展不会相互影响。
2.Vue.js 框架中响应式原理与观察者模式​

Vue.js 是一款盛行的前端 JavaScript 框架,其焦点的响应式原理就运用了观察者模式。当数据发生变化时,Vue 会自动更新 DOM,这背后的机制就是观察者模式的体现。​
在 Vue 中,每个组件实例都对应一个Watcher实例,Watcher就是观察者。数据对象则是被观察对象。当数据对象的属性发生变化时,会通知全部依赖它的Watcher,Watcher再去更新对应的 DOM。​
下面是一个简单的 Vue 响应式原理模仿代码示例:
  1. // 被观察对象
  2. function data() {
  3.     return {
  4.         message: 'Hello, Vue!'
  5.     }
  6. }
  7. // 观察者
  8. function Watcher(vm, expOrFn, cb) {
  9.     this.vm = vm;
  10.     this.cb = cb;
  11.     this.getter = parsePath(expOrFn);
  12.     this.value = this.get();
  13. }
  14. Watcher.prototype = {
  15.     get: function() {
  16.         Dep.target = this;
  17.         var value = this.getter.call(this.vm, this.vm);
  18.         Dep.target = null;
  19.         return value;
  20.     },
  21.     update: function() {
  22.         var oldValue = this.value;
  23.         this.value = this.get();
  24.         this.cb.call(this.vm, this.value, oldValue);
  25.     }
  26. };
  27. // 依赖收集器,管理观察者
  28. function Dep() {
  29.     this.subs = [];
  30. }
  31. Dep.prototype = {
  32.     addSub: function(sub) {
  33.         this.subs.push(sub);
  34.     },
  35.     notify: function() {
  36.         this.subs.forEach(function(sub) {
  37.             sub.update();
  38.         });
  39.     }
  40. };
  41. // 数据劫持
  42. function defineReactive(obj, key, val) {
  43.     var dep = new Dep();
  44.     Object.defineProperty(obj, key, {
  45.         enumerable: true,
  46.         configurable: true,
  47.         get: function() {
  48.             if (Dep.target) {
  49.                 dep.addSub(Dep.target);
  50.             }
  51.             return val;
  52.         },
  53.         set: function(newVal) {
  54.             if (newVal === val) return;
  55.             val = newVal;
  56.             dep.notify();
  57.         }
  58.     });
  59.     return val;
  60. }
  61. // 测试
  62. var vm = {};
  63. var dataObj = data();
  64. Object.keys(dataObj).forEach(function(key) {
  65.     defineReactive(vm, key, dataObj[key]);
  66. });
  67. // 添加观察者
  68. new Watcher(vm,'message', function(newVal, oldVal) {
  69.     console.log('message changed from', oldVal, 'to', newVal);
  70. });
  71. // 修改数据触发更新
  72. vm.message = 'New message';
复制代码
defineReactive函数将数据对象的属性举行劫持,当属性被访问和修改时举行依赖网络和通知。Watcher实例作为观察者,在数据变化时会收到通知并执行回调函数。
3.Spring 框架内建事件机制剖析 

Spring 框架的事件机制是基于观察者模式实现的,它提供了一种松耦合的方式来实现组件之间的通信和交互 。在 Spring 中,主要涉及三个焦点概念:事件(Event)、事件监听器(EventListener)和事件发布者(EventPublisher)。​
事件是应用程序中发生的特定事情的抽象表现,它继承自ApplicationEvent类 。开辟者可以根据业务需求自界说事件类,用于封装与事件相关的数据。我们可以界说OrderCreatedEvent类来表现订单创建事件,该类可以包含订单的详细信息,如订单编号、订单金额、下单时间等。​
  1. import org.springframework.context.ApplicationEvent;
  2. // 自定义事件
  3. public class OrderCreatedEvent extends ApplicationEvent {
  4.     private final Order order;
  5.     public OrderCreatedEvent(Object source, Order order) {
  6.         super(source);
  7.         this.order = order;
  8.     }
  9.     public Order getOrder() {
  10.         return order;
  11.     }
  12. }
复制代码
事件监听器负责监听特定范例的事件,并在事件发生时执行相应的业务逻辑 。它实现了ApplicationListener接口,重写onApplicationEvent方法。在该方法中,监听器可以获取事件对象,并根据事件携带的数据举行处理。当OrderCreatedEvent事件发生时,订单处理监听器可以获取订单信息,举行库存检查、订单状态更新等操作。​
  1. import org.springframework.context.ApplicationListener;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class OrderCreatedListener implements ApplicationListener<OrderCreatedEvent> {
  5.     @Override
  6.     public void onApplicationEvent(OrderCreatedEvent event) {
  7.         Order order = event.getOrder();
  8.         // 处理订单创建后的逻辑,如库存检查等
  9.         System.out.println("Processing order: " + order.getOrderNumber());
  10.     }
  11. }
复制代码
事件发布者负责发布事件,它通过注入ApplicationEventPublisher对象,调用publishEvent方法来发布事件 。当某个业务逻辑执行完成后,如订单创建乐成,事件发布者可以发布相应的事件,通知全部注册的监听器。
在订单服务中,当创建订单的方法执行乐成后,调用applicationEventPublisher.publishEvent(new OrderCreatedEvent(this, order))来发布订单创建事件,其中this表现事件源,order是订单对象 。通过这种方式,Spring 框架的事件机制实现了组件之间的解耦,提高了系统的可扩展性和维护性。
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.ApplicationEventPublisher;
  3. import org.springframework.stereotype.Service;
  4. @Service
  5. public class OrderService {
  6.     @Autowired
  7.     private ApplicationEventPublisher applicationEventPublisher;
  8.     public void createOrder(Order order) {
  9.         // 创建订单逻辑
  10.         System.out.println("Order created: " + order.getOrderNumber());
  11.         // 发布订单创建事件
  12.         applicationEventPublisher.publishEvent(new OrderCreatedEvent(this, order));
  13.     }
  14. }
复制代码
四、观察者模式的简单业务实现​:音乐播放系统状态变动通知场景构建

在音乐播放系统中,播放状态的变动须要及时通知到多个相关模块,如歌词显示模块、歌曲进度模块、用户通知模块等。以一个简单的音乐播放流程为例,当用户点击播放按钮后,播放状态从 “停息” 变为 “播放中”,此时须要通知歌词显示模块开始滚动歌词,通知歌曲进度模块实时更新进度,同时给用户发送播放提示消息。随着歌曲的停息、切换、结束等状态的不绝变化,都须要及时准确地通知到各个相关模块,以包管用户体验的流通性。​
在这个场景中,音乐播放对象充当被观察对象(Subject),歌词显示模块、歌曲进度模块、用户通知模块等则是观察对象(Observer)。当播放状态发生变化时,音乐播放对象会通知全部注册的观察对象,各个观察对象根据自身的业务逻辑举行相应的处理。
这种环境下我们可以基于 ApplicationEventPublisher 的观察者实现上述需求,在 Spring Boot 中,可以利用ApplicationEventPublisher来实现播放状态变动通知。起首,界说播放状态变动事件类:
  1. import org.springframework.context.ApplicationEvent;
  2. public class PlayStatusChangeEvent extends ApplicationEvent {
  3.     private String songId;
  4.     private String newStatus;
  5.     public PlayStatusChangeEvent(Object source, String songId, String newStatus) {
  6.         super(source);
  7.         this.songId = songId;
  8.         this.newStatus = newStatus;
  9.     }
  10.     public String getSongId() {
  11.         return songId;
  12.     }
  13.     public String getNewStatus() {
  14.         return newStatus;
  15.     }
  16. }
复制代码
在这个事件类中,PlayStatusChangeEvent继承自ApplicationEvent,通过构造函数传入事件源、歌曲 ID 和新的播放状态。​然后,我们在音乐播放服务中发布事件:
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.ApplicationEventPublisher;
  3. import org.springframework.stereotype.Service;
  4. @Service
  5. public class MusicPlayService {
  6.     @Autowired
  7.     private ApplicationEventPublisher applicationEventPublisher;
  8.     public void updatePlayStatus(String songId, String newStatus) {
  9.         // 业务逻辑处理,如更新播放状态到数据库
  10.         // ...
  11.         // 发布播放状态变更事件
  12.         applicationEventPublisher.publishEvent(new PlayStatusChangeEvent(this, songId, newStatus));
  13.     }
  14. }
复制代码
在MusicPlayService中,通过依赖注入获取ApplicationEventPublisher实例,在updatePlayStatus方法中,当播放状态更新完成后,调用publishEvent方法发布PlayStatusChangeEvent事件。​接着,界说事件监听器来处理事件:
  1. import org.springframework.context.ApplicationListener;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class LyricsDisplayListener implements ApplicationListener<PlayStatusChangeEvent> {
  5.     @Override
  6.     public void onApplicationEvent(PlayStatusChangeEvent event) {
  7.         if ("播放中".equals(event.getNewStatus())) {
  8.             // 滚动歌词逻辑
  9.             System.out.println("歌词显示模块收到歌曲 " + event.getSongId() + " 已播放通知,开始滚动歌词操作");
  10.         }
  11.     }
  12. }
  13. @Component
  14. public class SongProgressListener implements ApplicationListener<PlayStatusChangeEvent> {
  15.     @Override
  16.     public void onApplicationEvent(PlayStatusChangeEvent event) {
  17.         if ("播放中".equals(event.getNewStatus())) {
  18.             // 更新进度逻辑
  19.             System.out.println("歌曲进度模块收到歌曲 " + event.getSongId() + " 已播放通知,实时更新进度操作");
  20.         }
  21.     }
  22. }
  23. @Component
  24. public class UserNotificationListener implements ApplicationListener<PlayStatusChangeEvent> {
  25.     @Override
  26.     public void onApplicationEvent(PlayStatusChangeEvent event) {
  27.         // 发送通知逻辑
  28.         System.out.println("用户通知模块收到歌曲 " + event.getSongId() + " 状态变更为 " + event.getNewStatus() + " 的通知,发送通知操作");
  29.     }
  30. }
复制代码
这里我们分别界说了LyricsDisplayListener、SongProgressListener和UserNotificationListener三个事件监听器,它们实现了ApplicationListener接口,重写onApplicationEvent方法,在方法中根据不同的播放状态举行相应的业务处理。
但是在实际业务中,播放状态变动通知的处理大概涉及一些耗时操作,如从网络加载歌词、调用第三方音乐接口等。如果这些操作在主线程中同步执行,会导致播放状态更新的响应时间变长,影响用户体验。因此,引入异步事件处理机制是很有须要的。​
在 Spring Boot 中,我们可以使用@Async注解来实现异步事件处理。起首,在 Spring Boot 启动类上添加@EnableAsync注解,开启异步功能:
  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.scheduling.annotation.EnableAsync;
  4. @SpringBootApplication
  5. @EnableAsync
  6. public class Application {
  7.     public static void main(String[] args) {
  8.         SpringApplication.run(Application.class, args);
  9.     }
  10. }
复制代码
然后,在事件监听器的处理方法上添加@Async注解:
  1. import org.springframework.context.ApplicationListener;
  2. import org.springframework.scheduling.annotation.Async;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class UserNotificationListener implements ApplicationListener<PlayStatusChangeEvent> {
  6.     @Override
  7.     @Async
  8.     public void onApplicationEvent(PlayStatusChangeEvent event) {
  9.         // 发送通知逻辑,如发送推送消息
  10.         System.out.println("用户通知模块收到歌曲 " + event.getSongId() + " 状态变更为 " + event.getNewStatus() + " 的通知,发送通知操作");
  11.         // 模拟发送消息耗时操作
  12.         try {
  13.             Thread.sleep(2000);
  14.         } catch (InterruptedException e) {
  15.             e.printStackTrace();
  16.         }
  17.     }
  18. }
复制代码
在UserNotificationListener的onApplicationEvent方法上添加@Async注解后,当该方法被调用时,Spring 会将其放入一个线程池中异步执行,不会壅闭主线程。这样,播放状态更新的操作可以快速返回,提高了系统的响应性能。同时,通过公道设置线程池参数,可以控制异步任务的并发执行数量,避免资源耗尽等问题。
五、总结

观察者模式的本质是界说了对象之间一种动态的通讯契约,它构建起了被观察对象与观察对象之间松耦合的交互桥梁。通过这种契约关系,被观察对象专注于自身状态的维护和变化,当状态发生改变时,依据契约自动触发通知机制,将变化告知全部与之关联的观察对象 。而观察对象则依据契约中界说的更新方法,对收到的通知做出相应的反应,执行自身的业务逻辑。这种动态通讯契约使得系统中的各个对象能够在保持相对独立的同时,实现高效的信息交互和协同工作,极大地提高了系统的灵活性和可扩展性 。
其实说了这么多,大家肯定会发现,这个模式很像发布订阅模式。这里我们就不得不说一下他们之间的对比区别。固然观察者模式和发布订阅模式在很多方面具有相似性,都涉及到对象之间的消息通报和依赖关系管理,但它们之间也存在一些显着的区别边界:​
界说和概念:观察者模式主要界说了对象间一种直接的一对多依赖关系,被观察对象(Subject)直接与观察对象(Observer)举行交互 。
而发布订阅模式中,发布者(Publisher)和订阅者(Subscriber)之间通过一个中心的事件调度中心(Event Channel)举行通信,两边并不直接交互 。​
布局和依赖关系:在观察者模式中,被观察对象须要维护一个观察者列表,并且须要了解观察者的具体接口,以便在状态变化时能够直接调用观察者的更新方法,因此被观察对象与观察者之间存在一定的耦合度 。
而在发布订阅模式中,发布者和订阅者都只与事件调度中心交互,它们之间没有直接的依赖关系,耦合度更低,实现了更彻底的解耦 。​
消息通知方式:观察者模式中,当被观察对象状态发生变化时,会主动调用全部注册观察者的更新方法,将变化通知给观察者 。
而发布订阅模式中,发布者将事件发布到事件调度中心后,由事件调度中心负责将事件推送给全部订阅了该事件的订阅者,订阅者并不知道事件是由哪个发布者发布的 。​
应用场景:观察者模式更实用于在一个系统内部,模块之间存在明白的依赖关系,并且须要及时举行状态同步和消息通知的场景 。
发布订阅模式则更实用于在分布式系统、跨系统通信或者须要实现更灵活的事件处理机制的场景 。比方,在一个微服务架构中,不同的服务之间通过消息队列举行事件的发布和订阅,实现服务之间的解耦和异步通信 。
另外在我们使用观察者模式时,一定要注意循环引用和内存走漏的问题 。
循环引用通常发生在被观察对象和观察对象之间相互持有对方的引用 。比方,被观察对象在其观察者列表中持有观察对象的引用,而观察对象在其内部状态中又持有被观察对象的引用。这样,当程序试图释放其中一个对象时,由于对方持有自己的引用,导致垃圾接纳器无法接纳这些对象,从而造成内存走漏 。​
为了避免循环引用内存走漏,大家可以采取以下步伐:​


  • 及时移除观察者:在观察对象不再须要接收被观察对象的通知时,要及时调用被观察对象的移除方法,将观察对象从观察者列表中移除 。比方,在一个 Android 应用中,Activity 作为观察对象注册到某个数据模子(被观察对象)上,当 Activity 销毁时,要确保调用数据模子的移除方法,将 Activity 从观察者列表中移除,避免内存走漏 。​


  • 使用弱引用:可以使用弱引用(WeakReference)来持有观察者对象 。在 Java 中,可以通过WeakReference类来实现。使用弱引用后,即使被观察对象仍然持有观察者的弱引用,当观察者对象没有其他强引用指向它时,垃圾接纳器仍然可以接纳观察者对象,从而避免内存走漏。​


  • 公道计划对象生命周期:在计划系统时,要公道规划被观察对象和观察对象的生命周期,确保它们的生命周期相互匹配 。比方,避免创建生命周期过长的被观察对象,而观察对象的生命周期却很短,导致观察对象长时间无法被接纳 。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

前进之路

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表