(09)观察者模式
概念
定义对象之间的一对多依赖关系(一个被观察者,多个观察者),当被观察者的状态发生改变时,所有的观察者都得到通知。也称为发布-订阅模式。
- 吃蛙来哒需要在大众点评APP叫号排队,有A/B/C三人都在大众点评APP叫号,等待APP通知什么时候去吃蛙来哒。那么APP与A/B/C三人就形成了一对多的依赖关系。可以理解成A/B/C三人订阅了大众点评APP,当APP通知什么时候用餐时,A/B/C三人都能得到相应的消息
- 被观察者:大众点评APP。观察者:用户A/B/C
java实现方式
import java.util.ArrayList;
import java.util.List;
/**
* 观察者
*/
public class Demo {
public static void main(String[] args) {
App app = new App();
// app中添加两个观察者
app.add(new BinaryObserver());
app.add(new HexObserver());
// 当状态发生变化,则通知相应的观察者
app.setState(33);
//运行结果
//BinaryObserver 监听到状态发生变化:100001
//HexObserver 监听到状态发生变化:21
}
}
// 事件,当被观察者状态发生变化,则给观察者发送一个事件,一般事件中传递被观察者对象.
// 事件中还能记录发送时间等额外信息
class Event {
private App source;
public Event(App source) {
this.source = source;
}
public App getSource() {
return this.source;
}
}
// 被观察者
class App {
// 被观察者中,存储多个观察者
private List<Observer> observerList = new ArrayList<>();
private int state;
public void add(Observer observer) {
observerList.add(observer);
}
// 状态改变,通知所有观察者
public void setState(int state) {
this.state = state;
observerList.forEach(t -> t.listener(new Event(this)));
}
public int getState() {
return state;
}
}
// 观察者接口。所有观察者类都要实现该接口
interface Observer {
void listener(Event event);
}
// 具体观察者
class BinaryObserver implements Observer {
public void listener(Event event) {
System.out.println("BinaryObserver 监听到状态发生变化:" + Integer.toBinaryString(event.getSource().getState()));
}
}
// 具体观察者
class HexObserver implements Observer {
public void listener(Event event) {
System.out.println("HexObserver 监听到状态发生变化:" + Integer.toHexString(event.getSource().getState()));
}
}
BinaryObserver 监听到状态发生变化:100001
HexObserver 监听到状态发生变化:21
spring 观察者模式用法
普通代码写法
假设有个需求,创建订单成功后,需要保存订单日志,还要生成订单票据信息。
按以往代码逻辑,会是以下的写法。
@RestController
@RequestMapping("/order")
public class OrderController {
/**
* 创建订单接口
*/
@PostMapping("/createOrder")
public String createOrder() {
OrderEventMessage order = new OrderEventMessage();
order.setOrderId("1001");
order.setOrderName("苹果手机");
// 保存订单
orderService.createOrder(order);
// 保存订单日志
orderLogService.createOrderLog(order);
// 创建订单票据信息
orderInvoiceService.createOrderInvoice(order);
return "创建订单成功!";
}
}
上边写法,会存在以下问题
- 保存订单日志、订单票据时,同步阻塞导致响应变慢,需要异步非阻塞的解决方案
- 创建订单此时做的事情:创建订单、保存日志、创建单据,违反单一职责的原则。
- 如果后续来了个 订单积分 的需求,此订单接口还需要改动。
为了解决上述的问题,可以使用 spring 的 观察者模式
spring的观察者模式写法
- 先定义订单事件,使用spring的发布订阅模式,需要继承 ApplicationEvent 对象
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class OrderEvent extends ApplicationEvent {
private OrderEventMessage message;
public OrderEvent(Object source, OrderEventMessage message) {
super(source);
this.message = message;
}
}
- 定义订单消息,存储具体的订单信息
import lombok.Data;
import java.io.Serializable;
@Data
public class OrderEventMessage implements Serializable {
/** 订单号 */
private String orderId;
/** 订单名称 */
private String orderName;
}
- 定义订单监听器。订单监听器相当于前边java例子中 具体观察者的 listener 方法。
@EventListener: 使用注解的方式定义监听器
@Async: 开启新线程异步执行监听器的内容
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class OrderEventListener {
/**
* 订单日志监听器
*/
@EventListener
@Async
public void orderLogListener(OrderEvent event) {
System.out.println("====== orderLog ====== " + event.getMessage().getOrderName());
}
/**
* 订单票据监听器
*/
@EventListener
@Async
public void orderInvoiceListener(OrderEvent event) {
System.out.println("====== orderInvoice ====== " + event.getMessage().getOrderName());
}
}
- 发布事件
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
/**
* 创建订单接口
*/
@PostMapping("/createOrder")
public String createOrder() {
OrderEventMessage order = new OrderEventMessage();
order.setOrderId("1001");
order.setOrderName("苹果手机");
// 保存订单
orderService.createOrder(order);
// 发布事件
applicationEventPublisher.publishEvent(new OrderEvent(this, order));
return "创建订单成功!";
}
}
====== orderLog ====== 苹果手机
====== orderInvoice ====== 苹果手机
上边代码好处在于
- 创建订单接口只需要做创建订单的功能
- 订单创建好后,只需要把事件发布出去即可,后续有 订单积分 需求,此处也不需要修改代码,达到了解耦的目的

浙公网安备 33010602011771号