一、观察者模式核心定义
观察者模式是行为型设计模式的一种,核心目的是:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。
简单来说:一个 "主题"(被观察者)管理所有依赖它的 "观察者",主题状态变化时主动通知所有观察者,观察者收到通知后执行自己的逻辑。
核心解决的问题
- 解耦通知方与接收方:主题无需知道观察者的具体实现,只需维护观察者列表;观察者无需主动轮询主题状态,等待主题通知即可;
- 支持一对多联动:一个主题的状态变化可触发多个观察者的行为(如订单支付成功后,通知库存扣减、积分增加、日志记录);
- 动态扩展观察者:新增 / 移除观察者无需修改主题代码,符合开闭原则;
- 事件驱动编程:基于 "发布 - 订阅" 模型实现松耦合的事件处理。
生活类比
- 场景 1 :微信公众号
- 主题(被观察者):公众号;
- 观察者:关注公众号的用户;
- 核心:公众号发布新文章(状态变化),所有关注的用户都会收到推送通知,用户可选择查看 / 忽略。
- 场景 2 :股票行情
- 主题:股票(如茅台);
- 观察者:股民、券商 APP、财经网站;
- 核心:股票价格变动(状态变化),所有关注该股票的观察者都会收到价格更新通知。
- 场景 3 :报警系统
- 主题:温度传感器;
- 观察者:报警器、监控大屏、运维短信服务;
- 核心:温度超过阈值(状态变化),传感器通知所有观察者,报警器响、大屏告警、短信推送。
标准角色
| 角色 | 职责 | 类比(公众号场景) | JDK 对应类 / 接口 |
|---|---|---|---|
| 主题 / 被观察者(Subject) | 定义注册 / 移除观察者的方法,维护观察者列表;状态变化时通知所有观察者 | 公众号(管理关注者、推送文章) | java.util.Observable |
| 具体主题(ConcreteSubject) | 实现主题接口,存储状态;状态变化时触发通知逻辑 | 具体的公众号(如 "Java 编程思想") | 自定义业务主题类 |
| 观察者(Observer) | 定义接收通知的统一接口(如update()方法) |
公众号用户(接收推送的规范) | java.util.Observer |
| 具体观察者(ConcreteObserver) | 实现观察者接口,收到通知后执行具体业务逻辑 | 具体的用户(张三、李四) | 自定义业务观察者类 |
核心 UML 类图

二、公众号推送
以 "微信公众号推送" 为例,实现观察者模式的核心逻辑 ------ 覆盖所有核心角色,体现 "一对多" 的通知机制。
1. 步骤 1:定义观察者接口(公众号用户)
/**
* 观察者接口:公众号用户(定义接收推送的规范)
*/
public interface WeChatObserver {
/**
* 接收公众号推送的通知
* @param account 公众号名称
* @param content 推送内容
*/
void update(String account, String content);
}
2. 步骤 2:定义主题接口(公众号)
/**
* 主题接口:公众号(定义注册/移除观察者、推送消息的规范)
*/
public interface WeChatSubject {
/**
* 注册观察者(用户关注公众号)
*/
void registerObserver(WeChatObserver observer);
/**
* 移除观察者(用户取消关注)
*/
void removeObserver(WeChatObserver observer);
/**
* 通知所有观察者(推送消息)
*/
void notifyObservers();
/**
* 发布新文章(修改主题状态)
*/
void publishArticle(String content);
}
3. 步骤 3:实现具体主题(Java 编程公众号)
import java.util.ArrayList;
import java.util.List;
/**
* 具体主题:Java编程公众号(管理观察者、状态变化时通知)
*/
public class JavaProgramSubject implements WeChatSubject {
// 公众号名称
private final String accountName = "Java编程思想";
// 当前推送的文章内容(主题状态)
private String articleContent;
// 观察者列表(关注的用户)
private final List<WeChatObserver> observers = new ArrayList<>();
/**
* 注册观察者(用户关注)
*/
@Override
public void registerObserver(WeChatObserver observer) {
if (!observers.contains(observer)) {
observers.add(observer);
System.out.println("【主题-公众号】用户关注:" + observer.getClass().getSimpleName());
}
}
/**
* 移除观察者(用户取消关注)
*/
@Override
public void removeObserver(WeChatObserver observer) {
if (observers.contains(observer)) {
observers.remove(observer);
System.out.println("【主题-公众号】用户取消关注:" + observer.getClass().getSimpleName());
}
}
/**
* 通知所有观察者(核心:遍历观察者列表,调用update方法)
*/
@Override
public void notifyObservers() {
System.out.println("\n【主题-公众号】" + accountName + " 开始推送新文章:" + articleContent);
for (WeChatObserver observer : observers) {
observer.update(accountName, articleContent);
}
}
/**
* 发布新文章(修改状态后触发通知)
*/
@Override
public void publishArticle(String content) {
this.articleContent = content;
// 状态变化,通知所有观察者
notifyObservers();
}
}
4. 步骤 4:实现具体观察者(不同类型用户)
/**
* 具体观察者1:普通用户(接收推送后仅查看)
*/
public class NormalUserObserver implements WeChatObserver {
private final String username;
public NormalUserObserver(String username) {
this.username = username;
}
/**
* 接收推送通知(执行具体业务逻辑)
*/
@Override
public void update(String account, String content) {
System.out.println("【观察者-普通用户】" + username + " 收到 " + account + " 的推送:" + content + " → 已查看");
}
}
/**
* 具体观察者2:VIP用户(接收推送后收藏+点赞)
*/
public class VipUserObserver implements WeChatObserver {
private final String username;
public VipUserObserver(String username) {
this.username = username;
}
@Override
public void update(String account, String content) {
System.out.println("【观察者-VIP用户】" + username + " 收到 " + account + " 的推送:" + content + " → 已收藏+点赞");
}
}
/**
* 具体观察者3:运营监控(接收推送后统计阅读量)
*/
public class MonitorObserver implements WeChatObserver {
private int readCount = 0; // 累计阅读量
@Override
public void update(String account, String content) {
readCount++;
System.out.println("【观察者-运营监控】收到 " + account + " 的推送 → 累计阅读量:" + readCount);
}
public int getReadCount() {
return readCount;
}
}
5. 客户端(测试公众号推送)
/**
* 客户端:测试公众号观察者模式
*/
public class ObserverClient {
public static void main(String[] args) {
// 1. 创建主题(Java编程公众号)
WeChatSubject javaSubject = new JavaProgramSubject();
// 2. 创建观察者(不同类型用户)
WeChatObserver user1 = new NormalUserObserver("张三");
WeChatObserver user2 = new VipUserObserver("李四");
WeChatObserver monitor = new MonitorObserver();
// 3. 注册观察者(用户关注)
javaSubject.registerObserver(user1);
javaSubject.registerObserver(user2);
javaSubject.registerObserver(monitor);
// 4. 发布第一篇文章(状态变化,通知所有观察者)
javaSubject.publishArticle("《设计模式之观察者模式详解》");
// 5. 发布第二篇文章
javaSubject.publishArticle("《Spring Boot实战教程》");
// 6. 移除一个观察者(用户取消关注)
javaSubject.removeObserver(user1);
System.out.println();
// 7. 发布第三篇文章(仅通知剩余观察者)
javaSubject.publishArticle("《Java并发编程实战》");
// 8. 打印监控数据
System.out.println("\n【运营数据】累计阅读量:" + ((MonitorObserver) monitor).getReadCount());
}
}
输出结果
【主题-公众号】用户关注:NormalUserObserver
【主题-公众号】用户关注:VipUserObserver
【主题-公众号】用户关注:MonitorObserver
【主题-公众号】Java编程思想 开始推送新文章:《设计模式之观察者模式详解》
【观察者-普通用户】张三 收到 Java编程思想 的推送:《设计模式之观察者模式详解》 → 已查看
【观察者-VIP用户】李四 收到 Java编程思想 的推送:《设计模式之观察者模式详解》 → 已收藏+点赞
【观察者-运营监控】收到 Java编程思想 的推送 → 累计阅读量:1
【主题-公众号】Java编程思想 开始推送新文章:《Spring Boot实战教程》
【观察者-普通用户】张三 收到 Java编程思想 的推送:《Spring Boot实战教程》 → 已查看
【观察者-VIP用户】李四 收到 Java编程思想 的推送:《Spring Boot实战教程》 → 已收藏+点赞
【观察者-运营监控】收到 Java编程思想 的推送 → 累计阅读量:2
【主题-公众号】用户取消关注:NormalUserObserver
【主题-公众号】Java编程思想 开始推送新文章:《Java并发编程实战》
【观察者-VIP用户】李四 收到 Java编程思想 的推送:《Java并发编程实战》 → 已收藏+点赞
【观察者-运营监控】收到 Java编程思想 的推送 → 累计阅读量:3
【运营数据】累计阅读量:3
三、Spring 实战(订单支付事件通知)
在业务开发中,观察者模式最核心的实战场景是订单支付后的多系统联动 (如支付成功后,通知库存扣减、积分增加、日志记录、消息推送)。以下基于 Spring 的
ApplicationEvent和ApplicationListener实现观察者模式,这是 Spring 中最原生的事件通知方式。
1. 依赖准备(Spring Boot)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 核心模型定义
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.ApplicationEvent;
/**
* 订单支付事件(主题状态:封装支付成功的订单信息)
* 核心:继承ApplicationEvent,成为Spring可识别的事件(主题)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderPayEvent extends ApplicationEvent {
private String orderId; // 订单ID
private String userId; // 用户ID
private double amount; // 支付金额
private String payTime; // 支付时间
/**
* 构造函数必须调用父类构造(source为事件源)
*/
public OrderPayEvent(Object source, String orderId, String userId, double amount, String payTime) {
super(source);
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
this.payTime = payTime;
}
}
/**
* 订单DTO(业务传输对象)
*/
@Data
@AllArgsConstructor
public class OrderDTO {
private String orderId;
private String userId;
private double amount;
}
3. 具体观察者(事件监听器)
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 具体观察者1:库存监听器(支付成功后扣减库存)
* 核心:实现ApplicationListener,监听OrderPayEvent
*/
@Slf4j
@Component
public class StockListener implements ApplicationListener<OrderPayEvent> {
/**
* 收到支付事件后执行扣减库存逻辑
*/
@Override
public void onApplicationEvent(OrderPayEvent event) {
log.info("【观察者-库存监听器】订单支付成功,开始扣减库存 | 订单ID:{},用户ID:{}",
event.getOrderId(), event.getUserId());
// 模拟扣减库存逻辑
log.info("【观察者-库存监听器】库存扣减成功 | 订单ID:{}", event.getOrderId());
}
}
/**
* 具体观察者2:积分监听器(支付成功后增加用户积分)
*/
@Slf4j
@Component
public class PointListener implements ApplicationListener<OrderPayEvent> {
@Override
public void onApplicationEvent(OrderPayEvent event) {
log.info("【观察者-积分监听器】订单支付成功,开始增加积分 | 用户ID:{},支付金额:{}",
event.getUserId(), event.getAmount());
// 模拟增加积分:1元=1积分
int point = (int) event.getAmount();
log.info("【观察者-积分监听器】积分增加成功 | 用户ID:{},增加积分:{}", event.getUserId(), point);
}
}
/**
* 具体观察者3:日志监听器(支付成功后记录操作日志)
*/
@Slf4j
@Component
public class LogListener implements ApplicationListener<OrderPayEvent> {
@Override
public void onApplicationEvent(OrderPayEvent event) {
log.info("【观察者-日志监听器】记录订单支付日志 | 订单ID:{},支付时间:{},金额:{}",
event.getOrderId(), event.getPayTime(), event.getAmount());
// 模拟日志入库逻辑
log.info("【观察者-日志监听器】日志记录成功 | 订单ID:{}", event.getOrderId());
}
}
/**
* 具体观察者4:消息推送监听器(支付成功后推送短信)
*/
@Slf4j
@Component
public class MessageListener implements ApplicationListener<OrderPayEvent> {
@Override
public void onApplicationEvent(OrderPayEvent event) {
log.info("【观察者-消息监听器】订单支付成功,开始推送短信 | 用户ID:{},订单ID:{}",
event.getUserId(), event.getOrderId());
// 模拟短信推送逻辑
log.info("【观察者-消息监听器】短信推送成功 | 用户ID:{},内容:您的订单{}已支付成功,金额{}元",
event.getUserId(), event.getOrderId(), event.getAmount());
}
}
4. 主题发布者(订单服务)
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 主题发布者:订单服务(支付成功后发布事件)
* 核心:通过ApplicationContext发布事件(通知所有观察者)
*/
@Slf4j
@Service
public class OrderService {
// Spring上下文:用于发布事件(替代手动维护观察者列表)
@Resource
private ApplicationContext applicationContext;
/**
* 处理订单支付(支付成功后发布事件)
*/
public boolean payOrder(OrderDTO orderDTO) {
log.info("【主题-订单服务】开始处理订单支付 | 订单ID:{},用户ID:{},金额:{}",
orderDTO.getOrderId(), orderDTO.getUserId(), orderDTO.getAmount());
// 模拟支付逻辑(此处简化,直接返回成功)
boolean paySuccess = true;
if (paySuccess) {
// 1. 生成支付时间
String payTime = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
log.info("【主题-订单服务】订单支付成功 | 订单ID:{},支付时间:{}", orderDTO.getOrderId(), payTime);
// 2. 发布支付事件(核心:通知所有观察者)
OrderPayEvent payEvent = new OrderPayEvent(
this, // 事件源(当前服务)
orderDTO.getOrderId(),
orderDTO.getUserId(),
orderDTO.getAmount(),
payTime
);
applicationContext.publishEvent(payEvent);
log.info("【主题-订单服务】支付事件发布完成 | 订单ID:{}", orderDTO.getOrderId());
}
return paySuccess;
}
}
5. 客户端(Spring Boot 测试)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 客户端:测试订单支付事件通知
*/
@SpringBootApplication
public class SpringObserverDemoApplication {
public static void main(String[] args) {
// 1. 启动Spring容器(自动注册所有监听器)
ConfigurableApplicationContext context = SpringApplication.run(SpringObserverDemoApplication.class, args);
OrderService orderService = context.getBean(OrderService.class);
// 2. 测试订单支付(触发事件发布)
System.out.println("======= 测试订单支付事件通知 =======");
OrderDTO order = new OrderDTO("O20240313001", "U1001", 999.9);
boolean payResult = orderService.payOrder(order);
System.out.println("订单支付结果:" + payResult);
context.close();
}
}
输出结果
======= 测试订单支付事件通知 =======
【主题-订单服务】开始处理订单支付 | 订单ID:O20240313001,用户ID:U1001,金额:999.9
【主题-订单服务】订单支付成功 | 订单ID:O20240313001,支付时间:2024-03-13T15:30:00.123456
【观察者-库存监听器】订单支付成功,开始扣减库存 | 订单ID:O20240313001,用户ID:U1001
【观察者-库存监听器】库存扣减成功 | 订单ID:O20240313001
【观察者-积分监听器】订单支付成功,开始增加积分 | 用户ID:U1001,支付金额:999.9
【观察者-积分监听器】积分增加成功 | 用户ID:U1001,增加积分:999
【观察者-日志监听器】记录订单支付日志 | 订单ID:O20240313001,支付时间:2024-03-13T15:30:00.123456,金额:999.9
【观察者-日志监听器】日志记录成功 | 订单ID:O20240313001
【观察者-消息监听器】订单支付成功,开始推送短信 | 用户ID:U1001,订单ID:O20240313001
【观察者-消息监听器】短信推送成功 | 用户ID:U1001,内容:您的订单O20240313001已支付成功,金额999.9元
【主题-订单服务】支付事件发布完成 | 订单ID:O20240313001
订单支付结果:true
四、观察者模式的核心特点与适用场景
优点
- 解耦主题与观察者:主题无需知道观察者的具体实现,只需维护观察者列表;观察者无需知道主题的内部逻辑,只需处理通知;
- 支持动态扩展:新增 / 移除观察者无需修改主题代码,符合开闭原则;
- 一对多联动:一个主题的状态变化可触发多个观察者的行为,实现复杂的业务联动;
- 事件驱动:基于 "发布 - 订阅" 模型,实现异步 / 同步的事件处理;
- 简化依赖关系:避免主题与多个观察者之间的直接耦合,降低系统复杂度。
缺点
- 通知顺序不可控:默认情况下,观察者的通知顺序由主题的观察者列表顺序决定,复杂场景下难以保证执行顺序;
- 内存泄漏风险:如果观察者未正确移除(如匿名内部类),会导致主题持有观察者引用,造成内存泄漏;
- 性能损耗:观察者数量过多时,通知所有观察者会消耗大量时间;
- 异常传播:一个观察者的异常可能导致后续观察者无法收到通知(同步通知场景);
- 调试复杂:事件通知是隐式调用,定位问题时需跟踪整个通知链路。
适用场景
- 事件通知:订单状态变更、支付结果通知、用户注册成功后的多系统联动;
- 状态监控:系统监控(CPU / 内存 / 磁盘)、设备状态监控(传感器、物联网设备);
- 消息推送:公众号推送、短信推送、APP 消息推送;
- GUI 交互:按钮点击、文本框输入、下拉框选择等 UI 事件处理;
- 分布式系统:消息队列(Kafka/RabbitMQ)、微服务事件通知(Spring Cloud Event);
- 日志 / 监控:系统日志收集、操作审计、性能监控。
五、JDK/ Spring 中的原生应用(必须知道)
观察者模式是框架中最常用的设计模式之一,以下是核心场景:
1. JDK 核心观察者
- java.util.Observable :主题抽象类(已标记为过时,推荐使用自定义接口);
- java.util.Observer :观察者接口;
- java.beans.PropertyChangeListener :属性变更监听器(JavaBean 的属性变化通知);
- java.awt.event.EventListener :AWT/Swing 的事件监听器(如按钮点击、鼠标移动)。
2. Spring 核心观察者
- ApplicationEvent :Spring 事件基类(主题);
- ApplicationListener :Spring 事件监听器(观察者);
- ApplicationEventPublisher :事件发布器(主题的发布接口);
- @EventListener :注解式监听器(Spring 4.2+,替代实现 ApplicationListener);
- SmartApplicationListener :智能监听器(支持指定事件类型、控制执行顺序)。
3. 消息队列(Kafka/RabbitMQ)
- 主题:Topic/Exchange;
- 观察者:Consumer;
- 核心逻辑:生产者发布消息到主题,所有订阅该主题的消费者都会收到消息(分布式观察者模式)。
4. Android 事件系统
- 主题:View(按钮、文本框);
- 观察者:OnClickListener/OnTextChangedListener;
- 核心逻辑:View 状态变化时通知所有注册的监听器。
5. MyBatis 插件
- 主题:MyBatis 的执行流程(SQL 执行、参数处理);
- 观察者:Interceptor(拦截器);
- 核心逻辑:MyBatis 执行 SQL 时通知所有拦截器,拦截器可修改执行流程。
六、观察者模式 vs 发布 - 订阅模式(易混淆点)
很多人会混淆观察者模式和发布 - 订阅模式,其实发布 - 订阅是观察者模式的升级版:
| 维度 | 经典观察者模式 | 发布 - 订阅模式 |
|---|---|---|
| 核心结构 | 主题直接持有观察者列表,直接通知 | 引入 "消息中间件 / 事件总线",主题和观察者解耦 |
| 耦合度 | 主题与观察者轻度耦合(直接引用) | 完全解耦(主题 / 观察者只依赖中间件) |
| 通信方式 | 同步通信(默认) | 异步通信(主流) |
| 适用场景 | 单机内的事件通知 | 分布式系统的跨服务通知 |
| 典型实现 | JDK Observable、Spring ApplicationEvent | Kafka/RabbitMQ、EventBus |
总结
- 观察者模式的核心是定义一对多的依赖关系,主题状态变化时自动通知所有观察者,核心角色包括主题(被观察者)、观察者、具体主题、具体观察者;
- Spring 中通过
ApplicationEvent(事件)和ApplicationListener(监听器)实现观察者模式,是业务开发中最常用的事件通知方式; - 观察者模式适用于事件通知、状态监控、消息推送等场景,需注意控制观察者数量、处理异常传播、避免内存泄漏;
- 发布 - 订阅模式是观察者模式的分布式升级版,通过消息中间件实现主题与观察者的完全解耦,适用于跨服务的异步通知。
