观察者模式是一种对象行为型模式,定义对象间一对多的依赖关系,当一个对象(被观察者/主题)的状态发生改变时,所有依赖它的对象(观察者)都会收到通知并自动更新。
代码实现
比如说在订单业务中,当我们实现订单状态更新的时候,如下代码所示:
typescript
public class OrderServiceHardCode {
public void changeOrderStatus(String orderId,String oldStatus,String newStatus){
System.out.println("===== 订单" + orderId + "状态更新:" + oldStatus + "→" + newStatus + " =====");
SmsService.send(orderId, oldStatus, newStatus);
StockService.update(orderId, oldStatus, newStatus);
LogService.record(orderId, oldStatus, newStatus);
PointService.send(orderId, oldStatus, newStatus);
}
public static void main(String[] args) {
OrderServiceHardCode orderService = new OrderServiceHardCode();
// 模拟订单状态变更
orderService.changeOrderStatus("ORDER_123456", "待支付", "已支付");
}
}
// 短信工具类
class SmsService {
public static void send(String orderId, String oldStatus, String newStatus) {
System.out.println("【短信通知】订单" + orderId + "状态从" + oldStatus + "变更为" + newStatus + ",已通知用户!");
}
}
// 库存工具类
class StockService {
public static void update(String orderId, String oldStatus, String newStatus) {
if ("已支付".equals(newStatus)) {
System.out.println("【库存更新】订单" + orderId + "已支付,扣减对应商品库存!");
} else if ("已取消".equals(newStatus)) {
System.out.println("【库存更新】订单" + orderId + "已取消,恢复对应商品库存!");
}
}
}
// 日志工具类
class LogService {
public static void record(String orderId, String oldStatus, String newStatus) {
System.out.println("【日志记录】订单" + orderId + "状态变更:" + oldStatus + "→" + newStatus + ",操作时间:" + new Date());
}
}
// 积分工具类
class PointService {
public static void send(String orderId, String oldStatus, String newStatus) {
if ("已完成".equals(newStatus)) {
System.out.println("【积分发放】订单" + orderId + "已完成,为用户发放100积分!");
}
}
}
思考一下上面的实现有什么问题。

观察工具类的调用方法,方法参数一样,可以统一可以作为一个接口。如果我们后续再加或者减去工具类,那么原有的changeOrderStatus方法就必须要改动。每次都是通过具体的工具类去调用方法,这是针对具体编程,不方便以后扩展。根据OO设计思想我们要
针对接口编程,不针对实现编程
订单类与工具类订单类存在依赖关系,且是一对多的依赖关系,订单要通知这些工具类改变状态,所以我们可以考虑将上面代码用观察者模式实现。
先把工具类的方法都定义Observer接口的方法,让工具类都实现这个接口的方法。这个接口就是观察者,这些工具类就是具体的观察者对象,短信,库存,日志,积分观察者。然后我们再定义一个Subject(主题)的接口,让具体对象实现主题接口的方法,这个类也叫被观察者。被观察者也就是文中的OrderService,内部维护一个存储容器,用来注册和移除观察者,并通过通知方法来通知所有的观察者。如下代码所示:
typescript
public class OrderObserverTest {
public static void main(String[] args) {
// 1. 创建具体主题(订单服务)
OrderService orderService = new OrderService();
// 2. 注册所有观察者
orderService.registerObserver(new SmsNotifyObserver());
orderService.registerObserver(new StockUpdateObserver());
orderService.registerObserver(new LogRecordObserver());
orderService.registerObserver(new PointSendObserver());
// 3. 模拟订单状态变更:待支付→已支付
System.out.println("=== 测试1:订单待支付→已支付 ===");
orderService.notifyObservers("ORDER_123456", "待支付", "已支付");
System.out.println("\n=== 测试2:订单已支付→已完成 ===");
// 4. 模拟订单状态变更:已支付→已完成
orderService.notifyObservers("ORDER_123456", "已支付", "已完成");
System.out.println("\n=== 测试3:移除短信观察者后,订单已完成→已取消 ===");
// 5. 移除短信观察者(扩展能力)
orderService.removeObserver(new SmsNotifyObserver());
orderService.notifyObservers("ORDER_123456", "已完成", "已取消");
}
}
//主题
interface OrderSubject {
void registerObserver(OrderObserver observer);
void removeObserver(OrderObserver observer);
void notifyObservers(String orderId, String oldStatus, String newStatus);
}
class OrderService implements OrderSubject {
private List<OrderObserver> observers = new ArrayList<>();
@Override
public void registerObserver(OrderObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(OrderObserver observer) {
if (observer != null) {
observers.remove(observer);
}
}
@Override
public void notifyObservers(String orderId, String oldStatus, String newStatus) {
// 1. 先更新订单状态(实际业务中写入数据库)
System.out.println("===== 订单" + orderId + "状态更新:" + oldStatus + "→" + newStatus + " =====");
// 2. 通知所有观察者
for (OrderObserver observer : observers) {
observer.update(orderId, oldStatus, newStatus);
}
}
}
interface OrderObserver {
void update(String orderId, String oldStatus, String newStatus);
}
class SmsNotifyObserver implements OrderObserver {
@Override
public void update(String orderId, String oldStatus, String newStatus) {
System.out.println("【短信通知】订单" + orderId + "状态从" + oldStatus + "变更为" + newStatus + ",已通知用户!");
// 实际业务中:调用短信网关API发送短信
}
}
class StockUpdateObserver implements OrderObserver {
@Override
public void update(String orderId, String oldStatus, String newStatus) {
if ("已支付".equals(newStatus)) {
System.out.println("【库存更新】订单" + orderId + "已支付,扣减对应商品库存!");
} else if ("已取消".equals(newStatus)) {
System.out.println("【库存更新】订单" + orderId + "已取消,恢复对应商品库存!");
}
// 实际业务中:调用库存服务更新库存
}
}
class LogRecordObserver implements OrderObserver {
@Override
public void update(String orderId, String oldStatus, String newStatus) {
System.out.println("【日志记录】订单" + orderId + "状态变更:" + oldStatus + "→" + newStatus + ",操作时间:" + System.currentTimeMillis());
// 实际业务中:写入数据库/日志文件
}
}
class PointSendObserver implements OrderObserver {
@Override
public void update(String orderId, String oldStatus, String newStatus) {
if ("已完成".equals(newStatus)) {
System.out.println("【积分发放】订单" + orderId + "已完成,为用户发放100积分!");
}
// 实际业务中:调用积分服务发放积分
}
}
通过观察者实现代码以后,会发现订单类(主题)和工具类(观察者)实现了松耦合。订单类并不关心工具类的实现,工具类也不知道订单类的代码细节,但它们依旧可以交互。改变主题或观察者其中一方,并不会影响另一方,因为两者是松耦合的,只要它们遵守接口,我们就可以自由的改变。松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象互相依赖降到了最低。
观察者模式提供了一种对象设计,让主题与观察者之间松耦合
核心结构(UML类图)

-
Subject(主题)
- 提供注册、删除和通知观察者对象的接口
- 每个主题可以有多个观察者
-
ConcreteSubject(具体主题)
- 具体主题总是实现主题接口
- 获取和设置状态的方法
- Notify方法用于在状态改变时更新所有观察者
-
Observer(观察者)
- 给观察者对象定义更新方法的接口
-
ConcreteObserver(具体观察者)
- 具体观察者实现接口更新方法
- 存储状态,这些状态和主题状态保持一致
当主题发生状态变更的时候,会通知到注册在主题的观察者。
框架中的应用
Spring的事件驱动模型(Spring Event) 是对观察者模式标准化实现,解耦事件发布者和监听者,参考代码所示:
csharp
public class SpringEventTest {
public static void main(String[] args) {
EventPublisher publisher = new EventPublisher();
// 2. 注册监听器(观察者)
publisher.addListener(new LogListener());
publisher.addListener(new InitListener());
// 3. 发布应用启动事件(状态变更)
System.out.println("=== 发布应用启动事件 ===");
publisher.publishEvent(new AppStartedEvent(publisher, "电商后台系统"));
// 4. 移除日志监听器,再次发布事件
System.out.println("\n=== 移除日志监听器后,发布事件 ===");
publisher.removeListener(new LogListener());
publisher.publishEvent(new AppStartedEvent(publisher, "用户中心系统"));
}
}
abstract class ApplicationEvent{
private final Object source;
private final long timestamp;
public ApplicationEvent(Object source) {
this.source = source;
this.timestamp = System.currentTimeMillis();
}
// getter
public Object getSource() { return source; }
public long getTimestamp() { return timestamp; }
}
//具体的状态
class AppStartedEvent extends ApplicationEvent {
private final String appName;
public AppStartedEvent(Object source, String appName) {
super(source);
this.appName = appName;
}
public String getAppName() {
return appName;
}
}
interface ApplicationListener<E extends ApplicationEvent> {
// 监听器方法
void onApplicationEvent(E event);
}
class LogListener implements ApplicationListener<AppStartedEvent> {
@Override
public void onApplicationEvent(AppStartedEvent event) {
System.out.println("【日志监听器】应用" + event.getAppName() + "于" + event.getTimestamp() + "启动完成!");
}
}
class InitListener implements ApplicationListener<AppStartedEvent> {
@Override
public void onApplicationEvent(AppStartedEvent event) {
System.out.println("【初始化监听器】应用" + event.getAppName() + "启动完成,开始加载缓存数据...");
// 模拟初始化缓存
System.out.println("【初始化监听器】缓存加载完成!");
}
}
class EventPublisher {
private final List<ApplicationListener> listeners=new ArrayList<>();
public void addListener(ApplicationListener listener) {
listeners.add(listener);
}
public void removeListener(ApplicationListener listener) {
listeners.remove(listener);
}
public void publishEvent(ApplicationEvent event) {
for (ApplicationListener listener : listeners) {
if (listener instanceof ApplicationListener) {
((ApplicationListener) listener).onApplicationEvent(event);
}
}
}
}
总结
把观察者模式类比现实生活中的报纸。出版社相当于主题,订阅报纸的人相当于观察者。出版社一旦发布新闻报纸,订阅报纸的人都能收到。如果系统中有对象间存在依赖关系,并且需要推送给依赖对象状态的场景,试着用用观察者模式吧!