一、从一个问题开始
假设你正在开发一个天气预报系统,当天气数据更新时,需要通知多个显示设备:
java
csharp
public class WeatherData {
private float temperature;
private float humidity;
private float pressure;
// 问题:每次新增显示设备,都要修改这个类
private CurrentConditionsDisplay currentDisplay;
private StatisticsDisplay statisticsDisplay;
private ForecastDisplay forecastDisplay;
public void measurementsChanged() {
currentDisplay.update(temperature, humidity, pressure);
statisticsDisplay.update(temperature, humidity, pressure);
forecastDisplay.update(temperature, humidity, pressure);
// 每加一个设备,都要加一行调用
}
}
问题:
| 问题 | 说明 |
|---|---|
| 紧耦合 | 天气数据和显示设备直接绑定 |
| 违反开闭原则 | 新增显示设备要修改核心类 |
| 难以复用 | 显示设备无法在其他地方使用 |
| 难以测试 | 无法单独测试显示设备 |
观察者模式的解决方案 :天气数据作为被观察者(主题) ,显示设备作为观察者,两者通过接口解耦。
二、观察者模式是什么?
一句话定义 :定义对象间的一种一对多依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并自动更新。
通俗理解:
- 就像微信公众号:你关注了公众号(订阅),它发文章时你会收到推送
- 就像股票交易:你订阅某只股票的行情,价格变化时会收到通知
- 就像QQ群消息:你在群里,群友发消息你都能收到
角色结构:
text
scss
┌─────────────────────────────────────────────────────────────┐
│ Subject(主题接口) │
│ + registerObserver(Observer) │
│ + removeObserver(Observer) │
│ + notifyObservers() │
└─────────────────────────────────────────────────────────────┘
△
│ 实现
┌─────────────────────────────────────────────────────────────┐
│ ConcreteSubject(具体主题) │
│ - observers: List<Observer> │
│ - state │
│ + 业务方法 │
└─────────────────────────────────────────────────────────────┘
│
│ 通知
▼
┌─────────────────────────────────────────────────────────────┐
│ Observer(观察者接口) │
│ + update(state) │
└─────────────────────────────────────────────────────────────┘
△
│ 实现
┌───────────────┼───────────────┐
│ │ │
┌───────┴───────┐ ┌─────┴──────┐ ┌───────┴───────┐
│ ConcreteObs A │ │ConcreteObs B│ │ConcreteObs C │
└───────────────┘ └────────────┘ └───────────────┘
三、代码实现:天气预报系统
3.1 观察者模式基础实现
java
csharp
import java.util.ArrayList;
import java.util.List;
/**
* 观察者接口
*/
public interface Observer {
void update(float temperature, float humidity, float pressure);
}
/**
* 主题接口
*/
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
/**
* 具体主题:天气数据
*/
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
System.out.println("新增观察者,当前观察者数量:" + observers.size());
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
System.out.println("移除观察者,当前观察者数量:" + observers.size());
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
/**
* 当天气数据更新时,自动通知所有观察者
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public void measurementsChanged() {
notifyObservers();
}
}
/**
* 具体观察者:当前天气显示
*/
public class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
private String name;
public CurrentConditionsDisplay(String name, Subject weatherData) {
this.name = name;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println(name + " - 当前天气:温度 " + temperature + "°C,湿度 " + humidity + "%");
}
}
/**
* 具体观察者:统计显示
*/
public class StatisticsDisplay implements Observer {
private List<Float> temperatures = new ArrayList<>();
private String name;
public StatisticsDisplay(String name, Subject weatherData) {
this.name = name;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
temperatures.add(temperature);
display();
}
public void display() {
float avg = (float) temperatures.stream().mapToDouble(Float::doubleValue).average().orElse(0);
System.out.println(name + " - 平均温度:" + avg + "°C");
}
}
/**
* 具体观察者:天气预报
*/
public class ForecastDisplay implements Observer {
private float lastPressure;
private float currentPressure;
private String name;
public ForecastDisplay(String name, Subject weatherData) {
this.name = name;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
public void display() {
System.out.print(name + " - 天气预报:");
if (currentPressure > lastPressure) {
System.out.println("天气正在变好");
} else if (currentPressure < lastPressure) {
System.out.println("天气正在变差");
} else {
System.out.println("天气稳定");
}
}
}
/**
* 客户端
*/
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
// 创建观察者并自动注册
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay("手机App", weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay("气象站", weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay("电视台", weatherData);
System.out.println("\n=== 第一次天气更新 ===");
weatherData.setMeasurements(25.5f, 65, 1013.2f);
System.out.println("\n=== 移除电视台观察者 ===");
weatherData.removeObserver(forecastDisplay);
System.out.println("\n=== 第二次天气更新 ===");
weatherData.setMeasurements(27.8f, 70, 1011.5f);
System.out.println("\n=== 新增智能家居观察者 ===");
SmartHomeDisplay smartHome = new SmartHomeDisplay("智能家居", weatherData);
System.out.println("\n=== 第三次天气更新 ===");
weatherData.setMeasurements(26.0f, 68, 1014.0f);
}
}
输出:
text
ini
新增观察者,当前观察者数量:1
新增观察者,当前观察者数量:2
新增观察者,当前观察者数量:3
=== 第一次天气更新 ===
手机App - 当前天气:温度 25.5°C,湿度 65.0%
气象站 - 平均温度:25.5°C
电视台 - 天气预报:天气稳定
=== 移除电视台观察者 ===
移除观察者,当前观察者数量:2
=== 第二次天气更新 ===
手机App - 当前天气:温度 27.8°C,湿度 70.0%
气象站 - 平均温度:26.65°C
=== 新增智能家居观察者 ===
新增观察者,当前观察者数量:3
=== 第三次天气更新 ===
手机App - 当前天气:温度 26.0°C,湿度 68.0%
气象站 - 平均温度:26.433334°C
智能家居 - 检测到天气变化,关闭窗帘、调整空调
3.2 使用 Java 内置观察者模式
java
java
import java.util.Observable;
import java.util.Observer;
/**
* 具体主题(使用 Java 内置支持)
*/
public class WeatherDataV2 extends Observable {
private float temperature;
private float humidity;
private float pressure;
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public void measurementsChanged() {
// 标记状态已改变
setChanged();
// 通知所有观察者(推模式)
notifyObservers(new Object[]{temperature, humidity, pressure});
// 也可以使用拉模式:notifyObservers()
}
// 拉模式需要提供 getter
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
public float getPressure() { return pressure; }
}
/**
* 具体观察者(拉模式)
*/
public class CurrentConditionsDisplayV2 implements Observer {
private Observable observable;
private float temperature;
private float humidity;
public CurrentConditionsDisplayV2(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherDataV2) {
WeatherDataV2 weatherData = (WeatherDataV2) o;
// 拉模式:主动获取数据
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
public void display() {
System.out.println("当前天气:" + temperature + "°C," + humidity + "%");
}
}
四、实战场景
4.1 Spring 事件机制
Spring 基于观察者模式实现了事件发布-订阅机制。
java
typescript
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 自定义事件
*/
public class OrderEvent extends ApplicationEvent {
private String orderId;
private String status;
public OrderEvent(Object source, String orderId, String status) {
super(source);
this.orderId = orderId;
this.status = status;
}
public String getOrderId() { return orderId; }
public String getStatus() { return status; }
}
/**
* 事件发布者
*/
@Component
public class OrderService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void createOrder(String orderId) {
System.out.println("创建订单:" + orderId);
// 发布订单创建事件
publisher.publishEvent(new OrderEvent(this, orderId, "CREATED"));
}
public void payOrder(String orderId) {
System.out.println("支付订单:" + orderId);
publisher.publishEvent(new OrderEvent(this, orderId, "PAID"));
}
}
/**
* 事件监听器1:发送短信通知
*/
@Component
public class SmsListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent event) {
System.out.println("【短信】订单 " + event.getOrderId() + " 状态变更为:" + event.getStatus());
}
}
/**
* 事件监听器2:记录日志
*/
@Component
public class LogListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent event) {
System.out.println("【日志】订单 " + event.getOrderId() + " - " + event.getStatus() + " - " + new Date());
}
}
/**
* 事件监听器3:更新库存
*/
@Component
public class StockListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent event) {
if ("PAID".equals(event.getStatus())) {
System.out.println("【库存】订单 " + event.getOrderId() + " 支付成功,扣减库存");
}
}
}
// 使用
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/order")
public String createOrder() {
orderService.createOrder("ORD_001");
orderService.payOrder("ORD_001");
return "success";
}
}
4.2 消息队列的发布-订阅
java
typescript
/**
* 消息发布者
*/
public class MessagePublisher {
private List<MessageSubscriber> subscribers = new ArrayList<>();
public void subscribe(MessageSubscriber subscriber) {
subscribers.add(subscriber);
}
public void unsubscribe(MessageSubscriber subscriber) {
subscribers.remove(subscriber);
}
public void publish(String topic, String message) {
System.out.println("发布消息到 [" + topic + "]:" + message);
for (MessageSubscriber subscriber : subscribers) {
if (subscriber.getTopic().equals(topic)) {
subscriber.onMessage(message);
}
}
}
}
/**
* 订阅者接口
*/
public interface MessageSubscriber {
String getTopic();
void onMessage(String message);
}
/**
* 邮件订阅者
*/
public class EmailSubscriber implements MessageSubscriber {
private String email;
private String topic;
public EmailSubscriber(String email, String topic) {
this.email = email;
this.topic = topic;
}
@Override
public String getTopic() { return topic; }
@Override
public void onMessage(String message) {
System.out.println("发送邮件到 " + email + ":【" + topic + "】" + message);
}
}
/**
* Webhook订阅者
*/
public class WebhookSubscriber implements MessageSubscriber {
private String url;
private String topic;
public WebhookSubscriber(String url, String topic) {
this.url = url;
this.topic = topic;
}
@Override
public String getTopic() { return topic; }
@Override
public void onMessage(String message) {
System.out.println("调用Webhook " + url + ":【" + topic + "】" + message);
}
}
// 使用
MessagePublisher publisher = new MessagePublisher();
publisher.subscribe(new EmailSubscriber("user@example.com", "订单"));
publisher.subscribe(new WebhookSubscriber("https://api.example.com/order", "订单"));
publisher.subscribe(new EmailSubscriber("admin@example.com", "告警"));
publisher.publish("订单", "新订单 #12345 已创建");
publisher.publish("告警", "服务器 CPU 使用率超过 90%");
4.3 股票行情系统
java
csharp
/**
* 股票主题
*/
public class StockSubject {
private Map<String, List<StockObserver>> observers = new HashMap<>();
public void registerObserver(String stockCode, StockObserver observer) {
observers.computeIfAbsent(stockCode, k -> new ArrayList<>()).add(observer);
}
public void removeObserver(String stockCode, StockObserver observer) {
List<StockObserver> list = observers.get(stockCode);
if (list != null) {
list.remove(observer);
}
}
public void notifyObservers(String stockCode, double price) {
List<StockObserver> list = observers.get(stockCode);
if (list != null) {
for (StockObserver observer : list) {
observer.update(stockCode, price);
}
}
}
public void setPrice(String stockCode, double price) {
System.out.println("\n股票 " + stockCode + " 价格变为:" + price);
notifyObservers(stockCode, price);
}
}
/**
* 观察者接口
*/
public interface StockObserver {
void update(String stockCode, double price);
}
/**
* 投资者观察者
*/
public class Investor implements StockObserver {
private String name;
private double buyThreshold;
private double sellThreshold;
public Investor(String name, double buyThreshold, double sellThreshold) {
this.name = name;
this.buyThreshold = buyThreshold;
this.sellThreshold = sellThreshold;
}
@Override
public void update(String stockCode, double price) {
if (price <= buyThreshold) {
System.out.println(name + ":价格跌到 " + price + ",建议买入 " + stockCode);
} else if (price >= sellThreshold) {
System.out.println(name + ":价格涨到 " + price + ",建议卖出 " + stockCode);
} else {
System.out.println(name + ":" + stockCode + " 当前价格 " + price + ",观望中");
}
}
}
// 使用
StockSubject stockMarket = new StockSubject();
Investor alice = new Investor("张三", 95, 110);
Investor bob = new Investor("李四", 98, 108);
Investor carol = new Investor("王五", 90, 120);
stockMarket.registerObserver("000001", alice);
stockMarket.registerObserver("000001", bob);
stockMarket.registerObserver("000002", carol);
stockMarket.setPrice("000001", 100);
stockMarket.setPrice("000001", 96);
stockMarket.setPrice("000001", 112);
stockMarket.setPrice("000002", 95);
五、推模式 vs 拉模式
| 对比 | 推模式 | 拉模式 |
|---|---|---|
| 通知方式 | 主题把所有数据推给观察者 | 观察者主动从主题拉取数据 |
| 参数传递 | update(数据1, 数据2, 数据3) | update() 或 空参数 |
| 优点 | 观察者不需要知道主题细节 | 观察者按需获取数据 |
| 缺点 | 数据量大时效率低;数据变更要改接口 | 需要额外调用 getter |
| 适用场景 | 数据量小且固定 | 数据量大,观察者需要部分数据 |
java
java
// 推模式
public interface PushObserver {
void update(float temp, float humidity, float pressure);
}
// 拉模式
public interface PullObserver {
void update(WeatherData subject);
}
public class PullDisplay implements PullObserver {
@Override
public void update(WeatherData subject) {
// 按需获取数据
float temp = subject.getTemperature();
float humidity = subject.getHumidity();
System.out.println("温度:" + temp + ",湿度:" + humidity);
}
}
六、观察者模式 vs 其他模式
| 对比 | 观察者模式 | 发布-订阅模式 | 中介者模式 | 责任链模式 |
|---|---|---|---|---|
| 关系 | 一对多 | 多对多 | 多对多 | 链式 |
| 耦合度 | 低 | 极低(通过消息代理) | 中 | 低 |
| 是否知道对方 | 观察者知道主题 | 发布者和订阅者不知道对方 | 同事知道中介者 | 每个节点知道下一节点 |
| 典型例子 | GUI事件监听 | RabbitMQ、Kafka | MVC控制器 | 过滤器链 |
七、常见面试题
Q1:观察者模式的优缺点?
优点:
| 优点 | 说明 |
|---|---|
| 松耦合 | 主题和观察者之间只依赖接口 |
| 开闭原则 | 新增观察者不需要修改主题 |
| 支持广播通信 | 一个变化通知所有观察者 |
| 可动态注册 | 运行时可以添加/移除观察者 |
缺点:
| 缺点 | 说明 |
|---|---|
| 顺序不可控 | 观察者通知顺序不确定 |
| 性能问题 | 观察者过多时通知耗时 |
| 循环依赖 | 观察者和主题可能循环调用 |
| 内存泄漏 | 观察者忘记注销会导致内存泄漏 |
Q2:JDK 内置 Observable 为什么被废弃?
Observable是类不是接口,限制了灵活性- 状态管理不完善(
setChanged需要手动调用) - 通知顺序不可控
- 不支持泛型
- 推荐使用
PropertyChangeListener或第三方库
Q3:观察者模式和发布-订阅模式的区别?
| 区别 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 通信方式 | 主题直接通知观察者 | 通过消息代理(Broker) |
| 耦合度 | 松耦合(观察者知道主题) | 完全解耦(互不知道) |
| 同步/异步 | 通常同步 | 通常异步 |
| 典型实现 | 事件监听 | 消息队列(RabbitMQ、Kafka) |
Q4:如何避免观察者模式的内存泄漏?
java
typescript
// 解决方案1:使用弱引用
public class WeakObserverCache {
private Map<String, WeakReference<Observer>> observers = new HashMap<>();
public void register(String key, Observer observer) {
observers.put(key, new WeakReference<>(observer));
}
public void notifyAll() {
Iterator<Map.Entry<String, WeakReference<Observer>>> it = observers.entrySet().iterator();
while (it.hasNext()) {
Observer observer = it.next().getValue().get();
if (observer == null) {
it.remove(); // 自动清理
} else {
observer.update();
}
}
}
}
// 解决方案2:在观察者中正确注销
public class MyObserver implements Observer {
private Subject subject;
public MyObserver(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}
// 提供清理方法
public void dispose() {
subject.removeObserver(this);
}
}
八、总结与速记卡
核心要点
text
观察者模式 = 主题维护观察者列表 + 状态变化时通知所有观察者
代码模板
java
csharp
// 主题接口
public interface Subject {
void attach(Observer o);
void detach(Observer o);
void notify();
}
// 观察者接口
public interface Observer {
void update();
}
// 具体主题
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public void setState(int state) {
this.state = state;
notify();
}
public void attach(Observer o) { observers.add(o); }
public void detach(Observer o) { observers.remove(o); }
public void notify() {
for (Observer o : observers) {
o.update();
}
}
}
一句话记忆
| 场景 | 方案 |
|---|---|
| 一个变化影响多个对象 | 观察者模式 ✅ |
| 需要解耦事件发布和响应 | 观察者模式 ✅ |
| 微信公众号订阅 | 观察者模式 ✅ |
| GUI 按钮点击事件 | 观察者模式 ✅ |
| 分布式系统异步通信 | 发布-订阅模式 ✅ |