文章目录
-
- 前言
-
- 观察者模式是什么?
-
- 为什么要用观察者?能解决什么问题?
-
- 观察者模式的实现思路(Java 视角)
-
- 推模型(Push)+ 同步通知
-
- 4.1 角色定义:Observer 接口
- 4.2 角色定义:Subject 接口
- 4.3 具体主题:维护观察者列表并通知
- 4.4 两个具体观察者
- 4.5 测试运行
-
- 拉模型(Pull)实现思路
-
- 常见的优缺点
-
- 6.1 优点
- 6.2 缺点
-
- 常见实现方式(工程上怎么选)
-
- 7.1 同步方式(上面的示例)
- 7.2 异步方式(事件队列/线程池)
-
- 观察者模式的适用场景
-
- 注意事项(Java 工程常见坑)
-
- 一句话总结:怎么选实现方式?
前言
在软件开发中,"一个对象状态变化后,需要通知多个对象做相应处理"非常常见。比如:支付成功后要发短信、更新订单状态、写日志、触发埋点......如果让发布者直接依赖每个处理方,就会导致耦合度高、扩展困难 。
观察者模式 就是为了解决"发布-订阅的通知关系松耦合"问题:谁关心通知就订阅,发布者无需知道订阅者的具体实现。

1. 观察者模式是什么?
观察者模式(Observer Pattern):当一个对象(主题 Subject)的状态发生改变时,所有依赖它的对象(观察者 Observer)都会收到通知并自动更新。
常见用途:
- 事件驱动系统(UI 按钮点击、消息推送)
- 消息通知/监听机制(订单事件、告警事件)
- 框架内部组件解耦(Spring 事件机制属于这一思想)
2. 为什么要用观察者?能解决什么问题?
观察者模式通常用于以下场景:
- 一对多依赖:一个事件对应多个处理逻辑。
- 解耦发布者与订阅者:发布者不需要知道订阅者是谁、做什么。
- 动态可扩展:运行时可以增加/移除观察者,无需修改发布者代码。
- 统一事件通知机制:让业务逻辑围绕"事件"组织,而非硬编码调用链。
3. 观察者模式的实现思路(Java 视角)
GoF 观察者模式常见角色:
- Observer(观察者) :定义接收通知的方法,如
update(...) - ConcreteObserver(具体观察者) :实现
update(...),处理通知内容 - Subject(主题/被观察者) :维护观察者集合,如
attach/detach,并提供notify(...) - ConcreteSubject(具体主题) :状态变化时调用
notify(...)通知所有观察者
此外实现方式常见两类:
- 推模型(Push) :通知时直接把数据推给观察者 (如
update(message)) - 拉模型(Pull) :通知时只提醒观察者"有变化",数据由观察者自己再去主题读取
下面给出一个推模型同步通知示例。
4. 推模型(Push)+ 同步通知
4.1 角色定义:Observer 接口
java
public interface Observer {
void update(String message);
}
4.2 角色定义:Subject 接口
java
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers(String message);
}
4.3 具体主题:维护观察者列表并通知
java
import java.util.ArrayList;
import java.util.List;
public class NewsPublisher implements Subject {
private final List<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(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
// 状态变化触发通知
public void publish(String message) {
System.out.println("发布新闻: " + message);
notifyObservers(message);
}
}
4.4 两个具体观察者
java
public class EmailSubscriber implements Observer {
@Override
public void update(String message) {
System.out.println("邮件订阅收到: " + message);
}
}
java
public class WechatSubscriber implements Observer {
@Override
public void update(String message) {
System.out.println("微信订阅收到: " + message);
}
}
4.5 测试运行
java
public class ObserverDemo {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher();
Observer email = new EmailSubscriber();
Observer wechat = new WechatSubscriber();
publisher.attach(email);
publisher.attach(wechat);
publisher.publish("观察者模式上线啦!");
}
}
输出示例:
发布新闻: 观察者模式上线啦!
邮件订阅收到: 观察者模式上线啦!
微信订阅收到: 观察者模式上线啦!
5. 拉模型(Pull)实现思路
拉模型的关键点:notify 时不把具体数据传给观察者,而是让观察者通过主题"再取最新状态"。
典型变化:
update()可能不带参数 :void update()- 观察者在
update()内调用subject.getState()拉取数据
拉模型常见于:
- 数据更新频繁,通知只需"提醒"
- 希望降低通知参数复杂度
6. 常见的优缺点
6.1 优点
- 松耦合:发布者不依赖具体观察者实现
- 易扩展:新增观察者通常不改发布者逻辑
- 符合开放封闭原则:对扩展开放,对修改关闭
- 事件驱动:让系统结构更符合业务事件流
6.2 缺点
- 通知链不可控:观察者多时通知成本高
- 异常传播问题:某个观察者失败可能影响后续通知(需隔离 try-catch)
- 线程/时序复杂:多线程下 attach/detach/notify 需要线程安全策略
- 可能形成"隐式依赖":调试时不易追踪谁被通知了
7. 常见实现方式(工程上怎么选)
7.1 同步方式(上面的示例)
publish()会等待所有观察者update()完成- 简单直观,适合观察者处理很快的场景
7.2 异步方式(事件队列/线程池)
- 观察者通知由线程池执行,发布者不阻塞
- 常见于:消息通知、埋点上报、告警推送等
工程实践通常会结合:线程池 + 超时 + 重试/熔断 + 观测(日志/指标)。
8. 观察者模式的适用场景
适合:
- 一对多事件通知(同一事件多个处理方)
- 事件处理可扩展(订阅者经常增减)
- 希望降低发布者与处理方耦合度
- UI/业务事件驱动系统
不适合:
- 你需要强一致的"调用返回结果"链(观察者是通知,不是请求-响应)
- 订阅者数量极少且固定,直接调用反而更简单
- 观察者处理强依赖顺序且必须同步完成(可能需要更复杂的编排机制)
9. 注意事项(Java 工程常见坑)
- 线程安全
- 如果运行中频繁
attach/detach,且notify可能并发:- 可考虑
CopyOnWriteArrayList或加锁策略
- 可考虑
- 如果运行中频繁
- 异常隔离
notifyObservers中建议 try-catch,避免一个观察者异常导致后面都不执行
- 避免通知风暴
- 观察者过多/处理过慢时需要异步化或做合并/节流
10. 一句话总结:怎么选实现方式?
- 观察者模式(Observer) 适合"状态变化需要通知多个处理方,且希望发布者与订阅者松耦合"的场景;
- 通知数据传给观察者用推模型 ,只提醒观察者再去拉数据用拉模型;
- 工程上观察者多且慢时优先考虑异步通知。