设计模式 - 观察者模式
从一个实际场景说起
假设你在开发一个消息推送系统:
- 用户可以订阅不同的频道(如新闻频道、体育频道、科技频道)
- 当频道有新消息时,需要推送给所有订阅了该频道的用户
- 用户可以随时订阅或取消订阅
如果用传统方式实现,你可能会写出这样的代码:
public class NewsChannel {
private List<User> users = new ArrayList<>();
public void addUser(User user) {
users.add(user);
}
public void removeUser(User user) {
users.remove(user);
}
public void publishNews(String news) {
// 遍历所有用户,推送消息
for (User user : users) {
user.receive(news);
}
}
}
这看起来没问题,但如果需求变了:
- 新增用户类型:VIP用户、普通用户、企业用户
- 不同用户接收消息的方式不同:邮件、短信、APP推送
- 需要支持其他订阅对象,如日志系统、统计系统
你会发现代码越来越难维护,因为发布消息的逻辑和通知用户的逻辑耦合在一起。
观察者模式就是为了解决这种”一对多”的依赖关系而生的。
什么是观察者模式
定义
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。
核心思想
就像报纸订阅:
- 报社是被观察者,负责出版报纸
- 读者是观察者,订阅报纸
- 当有新报纸时,报社自动送给所有订阅的读者
角色分工
Subject(被观察者/主题)
├─ 持有观察者列表
├─ 提供注册/注销观察者的方法
└─ 状态改变时通知所有观察者
Observer(观察者)
├─ 定义更新接口
└─ 接收到通知后执行具体操作
ConcreteSubject(具体主题)
└─ 实现Subject接口,存储具体状态
ConcreteObserver(具体观察者)
└─ 实现Observer接口,定义具体的更新逻辑
实战示例:消息推送系统
完整实现
1. 定义观察者接口
/**
* 观察者接口
*/
public interface Observer {
/**
* 接收到消息时的更新方法
* @param message 消息内容
*/
void update(String message);
}
2. 定义被观察者接口
/**
* 被观察者接口(主题)
*/
public interface Subject {
/**
* 注册观察者
*/
void registerObserver(Observer observer);
/**
* 移除观察者
*/
void removeObserver(Observer observer);
/**
* 通知所有观察者
*/
void notifyObservers(String message);
}
3. 实现具体主题:消息频道
import java.util.ArrayList;
import java.util.List;
/**
* 消息频道(具体主题)
*/
public class MessageChannel implements Subject {
private String channelName;
private List<Observer> observers = new ArrayList<>();
public MessageChannel(String channelName) {
this.channelName = channelName;
}
@Override
public void registerObserver(Observer observer) {
if (!observers.contains(observer)) {
observers.add(observer);
System.out.println("[" + observer + "] 订阅了频道: " + channelName);
}
}
@Override
public void removeObserver(Observer observer) {
if (observers.remove(observer)) {
System.out.println("[" + observer + "] 取消订阅频道: " + channelName);
}
}
@Override
public void notifyObservers(String message) {
System.out.println("\n===== 频道【" + channelName + "】发布新消息 =====");
System.out.println("消息内容: " + message);
System.out.println("正在通知 " + observers.size() + " 个订阅者...\n");
for (Observer observer : observers) {
observer.update(message);
}
}
/**
* 发布消息(业务方法)
*/
public void publishMessage(String message) {
notifyObservers(message);
}
public String getChannelName() {
return channelName;
}
}
4. 实现具体观察者:用户
/**
* 用户观察者(通过邮件接收)
*/
public class EmailUser implements Observer {
private String username;
private String email;
public EmailUser(String username, String email) {
this.username = username;
this.email = email;
}
@Override
public void update(String message) {
System.out.println("【邮件通知】用户: " + username);
System.out.println(" 邮箱: " + email);
System.out.println(" 接收到消息: " + message);
System.out.println(" 已发送邮件到 " + email + "\n");
}
@Override
public String toString() {
return username;
}
}
/**
* 用户观察者(通过短信接收)
*/
public class SmsUser implements Observer {
private String username;
private String phone;
public SmsUser(String username, String phone) {
this.username = username;
this.phone = phone;
}
@Override
public void update(String message) {
System.out.println("【短信通知】用户: " + username);
System.out.println(" 手机: " + phone);
System.out.println(" 接收到消息: " + message);
System.out.println(" 已发送短信到 " + phone + "\n");
}
@Override
public String toString() {
return username;
}
}
/**
* 用户观察者(通过APP推送)
*/
public class AppUser implements Observer {
private String username;
private String deviceId;
public AppUser(String username, String deviceId) {
this.username = username;
this.deviceId = deviceId;
}
@Override
public void update(String message) {
System.out.println("【APP推送】用户: " + username);
System.out.println(" 设备ID: " + deviceId);
System.out.println(" 接收到消息: " + message);
System.out.println(" 已推送到设备 " + deviceId + "\n");
}
@Override
public String toString() {
return username;
}
}
5. 客户端使用
public class MessagePushDemo {
public static void main(String[] args) {
// 1. 创建消息频道(被观察者)
MessageChannel newsChannel = new MessageChannel("新闻频道");
// 2. 创建用户(观察者)
Observer user1 = new EmailUser("张三", "zhangsan@example.com");
Observer user2 = new SmsUser("李四", "13800138000");
Observer user3 = new AppUser("王五", "DEVICE_12345");
Observer user4 = new EmailUser("赵六", "zhaoliu@example.com");
// 3. 用户订阅频道
newsChannel.registerObserver(user1);
newsChannel.registerObserver(user2);
newsChannel.registerObserver(user3);
newsChannel.registerObserver(user4);
// 4. 频道发布消息,所有订阅者自动收到通知
newsChannel.publishMessage("今日头条:我国成功发射新一代通信卫星!");
// 5. 用户取消订阅
newsChannel.removeObserver(user2);
// 6. 再次发布消息
newsChannel.publishMessage("突发新闻:国际油价大幅上涨!");
// 7. 新用户订阅
Observer user5 = new SmsUser("孙七", "13900139000");
newsChannel.registerObserver(user5);
// 8. 发布消息
newsChannel.publishMessage("体育快讯:中国队晋级世界杯决赛!");
}
}
运行结果:
[张三] 订阅了频道: 新闻频道
[李四] 订阅了频道: 新闻频道
[王五] 订阅了频道: 新闻频道
[赵六] 订阅了频道: 新闻频道
===== 频道【新闻频道】发布新消息 =====
消息内容: 今日头条:我国成功发射新一代通信卫星!
正在通知 4 个订阅者...
【邮件通知】用户: 张三
邮箱: zhangsan@example.com
接收到消息: 今日头条:我国成功发射新一代通信卫星!
已发送邮件到 zhangsan@example.com
【短信通知】用户: 李四
手机: 13800138000
接收到消息: 今日头条:我国成功发射新一代通信卫星!
已发送短信到 13800138000
【APP推送】用户: 王五
设备ID: DEVICE_12345
接收到消息: 今日头条:我国成功发射新一代通信卫星!
已推送到设备 DEVICE_12345
【邮件通知】用户: 赵六
邮箱: zhaoliu@example.com
接收到消息: 今日头条:我国成功发射新一代通信卫星!
已发送邮件到 zhaoliu@example.com
[李四] 取消订阅频道: 新闻频道
===== 频道【新闻频道】发布新消息 =====
消息内容: 突发新闻:国际油价大幅上涨!
正在通知 3 个订阅者...
【邮件通知】用户: 张三
邮箱: zhangsan@example.com
接收到消息: 突发新闻:国际油价大幅上涨!
已发送邮件到 zhangsan@example.com
【APP推送】用户: 王五
设备ID: DEVICE_12345
接收到消息: 突发新闻:国际油价大幅上涨!
已推送到设备 DEVICE_12345
【邮件通知】用户: 赵六
邮箱: zhaoliu@example.com
接收到消息: 突发新闻:国际油价大幅上涨!
已发送邮件到 zhaoliu@example.com
[孙七] 订阅了频道: 新闻频道
===== 频道【新闻频道】发布新消息 =====
消息内容: 体育快讯:中国队晋级世界杯决赛!
正在通知 4 个订阅者...
【邮件通知】用户: 张三
邮箱: zhangsan@example.com
接收到消息: 体育快讯:中国队晋级世界杯决赛!
已发送邮件到 zhangsan@example.com
【APP推送】用户: 王五
设备ID: DEVICE_12345
接收到消息: 体育快讯:中国队晋级世界杯决赛!
已推送到设备 DEVICE_12345
【邮件通知】用户: 赵六
邮箱: zhaoliu@example.com
接收到消息: 体育快讯:中国队晋级世界杯决赛!
已发送邮件到 zhaoliu@example.com
【短信通知】用户: 孙七
手机: 13900139000
接收到消息: 体育快讯:中国队晋级世界杯决赛!
已发送短信到 13900139000
代码解析
工作流程:
- 订阅阶段:用户调用
registerObserver()订阅频道,频道将用户加入观察者列表 - 发布阶段:频道调用
publishMessage()发布消息,触发notifyObservers() - 通知阶段:遍历观察者列表,调用每个观察者的
update()方法 - 更新阶段:每个观察者根据自己的方式处理消息(邮件/短信/APP推送)
- 退订阶段:用户调用
removeObserver()取消订阅,从观察者列表移除
关键特点:
- 松耦合:频道不知道具体用户是谁,只知道观察者接口
- 可扩展:新增用户类型只需实现
Observer接口,无需修改频道代码 - 动态订阅:运行时可以随时订阅或取消订阅
- 广播通信:一次消息发布,所有订阅者都能收到
进阶示例:多频道消息推送
在实际应用中,用户可能订阅多个频道,每个频道有不同的消息:
改进版实现
/**
* 改进的观察者接口,包含频道信息
*/
public interface Observer {
void update(String channelName, String message);
}
/**
* 改进的消息频道
*/
public class MessageChannel implements Subject {
private String channelName;
private List<Observer> observers = new ArrayList<>();
public MessageChannel(String channelName) {
this.channelName = channelName;
}
@Override
public void notifyObservers(String message) {
System.out.println("\n===== 【" + channelName + "】发布新消息 =====");
for (Observer observer : observers) {
observer.update(channelName, message);
}
}
// 其他方法...
}
/**
* 用户可以订阅多个频道
*/
public class MultiChannelUser implements Observer {
private String username;
private Set<String> subscribedChannels = new HashSet<>();
public MultiChannelUser(String username) {
this.username = username;
}
@Override
public void update(String channelName, String message) {
subscribedChannels.add(channelName);
System.out.println("用户【" + username + "】收到【" + channelName + "】消息: " + message);
}
public void showSubscriptions() {
System.out.println("用户【" + username + "】订阅的频道: " + subscribedChannels);
}
}
// 使用
public class MultiChannelDemo {
public static void main(String[] args) {
// 创建多个频道
MessageChannel newsChannel = new MessageChannel("新闻频道");
MessageChannel sportsChannel = new MessageChannel("体育频道");
MessageChannel techChannel = new MessageChannel("科技频道");
// 创建用户
MultiChannelUser user = new MultiChannelUser("张三");
// 用户订阅多个频道
newsChannel.registerObserver(user);
sportsChannel.registerObserver(user);
techChannel.registerObserver(user);
// 各频道发布消息
newsChannel.publishMessage("今日头条:我国成功发射卫星");
sportsChannel.publishMessage("CBA总决赛:广东队夺冠");
techChannel.publishMessage("苹果发布新款iPhone");
// 查看用户订阅的所有频道
user.showSubscriptions();
}
}
输出:
===== 【新闻频道】发布新消息 =====
用户【张三】收到【新闻频道】消息: 今日头条:我国成功发射卫星
===== 【体育频道】发布新消息 =====
用户【张三】收到【体育频道】消息: CBA总决赛:广东队夺冠
===== 【科技频道】发布新消息 =====
用户【张三】收到【科技频道】消息: 苹果发布新款iPhone
用户【张三】订阅的频道: [新闻频道, 体育频道, 科技频道]
使用 Java 内置的观察者模式
Java 标准库提供了 java.util.Observable 和 java.util.Observer 类(已废弃,推荐自己实现),可以简化代码:
import java.util.Observable;
import java.util.Observer;
/**
* 使用Java内置Observable(已废弃,仅作示例)
*/
public class NewsChannel extends Observable {
private String channelName;
public NewsChannel(String channelName) {
this.channelName = channelName;
}
public void publishMessage(String message) {
System.out.println("\n【" + channelName + "】发布消息: " + message);
// 标记状态已改变
setChanged();
// 通知所有观察者
notifyObservers(message);
}
}
/**
* 使用Java内置Observer
*/
public class User implements Observer {
private String username;
public User(String username) {
this.username = username;
}
@Override
public void update(Observable o, Object arg) {
String message = (String) arg;
System.out.println("用户【" + username + "】收到消息: " + message);
}
}
// 使用
public class JavaObserverDemo {
public static void main(String[] args) {
NewsChannel channel = new NewsChannel("新闻频道");
User user1 = new User("张三");
User user2 = new User("李四");
channel.addObserver(user1);
channel.addObserver(user2);
channel.publishMessage("今日头条:科技重大突破!");
}
}
注意: Observable 和 Observer 在 Java 9 中已被标记为废弃,推荐自己实现观察者模式或使用事件框架。
Spring 中的事件机制
Spring 框架提供了完善的事件机制,本质就是观察者模式:
Spring 事件实现
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 自定义事件
*/
public class MessageEvent extends ApplicationEvent {
private String channelName;
private String message;
public MessageEvent(Object source, String channelName, String message) {
super(source);
this.channelName = channelName;
this.message = message;
}
public String getChannelName() { return channelName; }
public String getMessage() { return message; }
}
/**
* 事件发布者
*/
@Component
public class MessagePublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishMessage(String channelName, String message) {
MessageEvent event = new MessageEvent(this, channelName, message);
eventPublisher.publishEvent(event);
System.out.println("消息已发布: [" + channelName + "] " + message);
}
}
/**
* 事件监听器(观察者)
*/
@Component
public class EmailListener {
@EventListener
public void handleMessage(MessageEvent event) {
System.out.println("【邮件监听器】收到消息: " + event.getMessage());
}
}
@Component
public class SmsListener {
@EventListener
public void handleMessage(MessageEvent event) {
System.out.println("【短信监听器】收到消息: " + event.getMessage());
}
}
// 使用
@SpringBootApplication
public class SpringEventDemo implements CommandLineRunner {
@Autowired
private MessagePublisher publisher;
public static void main(String[] args) {
SpringApplication.run(SpringEventDemo.class, args);
}
@Override
public void run(String... args) {
publisher.publishMessage("新闻频道", "今日头条:重大科技突破!");
}
}
什么时候使用观察者模式
✅ 适用场景
事件处理系统
- 用户注册成功后,发送邮件、短信、记录日志
- 订单状态变更后,通知库存、支付、物流系统
消息推送系统
- 新闻订阅、股票价格变动通知
- 社交媒体的好友动态推送
GUI 事件处理
- 按钮点击事件
- 文本框内容变化监听
分布式系统
- 配置中心配置变更通知
- 服务注册与发现
关键判断标准
当你遇到以下情况时,考虑使用观察者模式:
- 一对多关系:一个对象的改变需要通知多个对象
- 松耦合需求:对象之间需要通信,但不能紧密耦合
- 动态订阅:运行时需要动态添加或删除观察者
- 事件驱动:系统采用事件驱动架构
观察者模式的优缺点
优点
- 解耦:被观察者和观察者之间是抽象耦合,符合依赖倒置原则
- 扩展性好:新增观察者无需修改被观察者代码,符合开闭原则
- 灵活:运行时可以动态添加、删除观察者
- 广播通信:一次触发,多个观察者响应
缺点
- 性能问题:观察者太多时,通知所有观察者会耗时较长
- 循环依赖:如果观察者和被观察者相互引用,可能导致循环调用
- 不知道具体变化:观察者只知道被观察者发生了变化,但不知道具体是什么变化
- 调试困难:采用异步方式时,调试和定位问题比较困难
最佳实践
1. 使用接口隔离观察者
// 好的做法:定义清晰的接口
public interface MessageObserver {
void onMessage(String message);
}
// 不好的做法:直接依赖具体类
public class User {
public void update(String message) { ... }
}
2. 处理异常,避免影响其他观察者
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
try {
observer.update(message);
} catch (Exception e) {
System.err.println("通知观察者失败: " + e.getMessage());
// 继续通知其他观察者,不要因为一个失败而中断
}
}
}
3. 考虑异步通知
@Override
public void notifyObservers(String message) {
observers.parallelStream().forEach(observer -> {
observer.update(message);
});
}
// 或使用线程池
private ExecutorService executor = Executors.newFixedThreadPool(10);
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
executor.submit(() -> observer.update(message));
}
}
4. 提供取消所有订阅的方法
public void clearObservers() {
observers.clear();
}
总结
核心要点
定义:定义对象间一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知
角色:
- Subject(被观察者):维护观察者列表,提供注册、注销、通知方法
- Observer(观察者):定义更新接口
- ConcreteSubject(具体主题):存储状态,状态改变时通知观察者
- ConcreteObserver(具体观察者):实现具体的更新逻辑
工作流程:
观察者注册 → 被观察者状态改变 → 通知所有观察者 → 观察者执行更新核心价值:
- 解耦:被观察者和观察者之间松耦合
- 扩展:新增观察者不影响被观察者
- 灵活:运行时动态管理观察者
一句话总结
观察者模式就是让被观察者维护一个观察者列表,当状态改变时自动通知所有观察者,实现对象间的松耦合通信。
就像订阅报纸一样,有新报纸时,报社自动送给所有订阅的读者,而不需要读者每天打电话询问。
作者:leejie
日期:2026年
标签:Java、设计模式、观察者模式、架构设计