【面试题】什么是观察者模式?一般用在什么场景?
观察者模式:消息界的"八卦小分队" 📢
一、什么是观察者模式?
想象一下微信群聊:
- 你发一条消息(发布事件)
- 群里所有人都收到了通知(观察者被触发)
- 有些人回复,有些人点赞,有些人潜水(不同的反应)
这就是观察者模式:一个对象(被观察者)的状态变化,会通知所有依赖它的对象(观察者)。
二、Java代码示例:明星出轨爆料现场 🎬
import java.util.*;
// 1. 观察者接口(吃瓜群众)
interface GossipObserver {
void update(String celebrityName, String scandal);
}
// 2. 具体观察者:不同反应的人
class ExcitedFan implements GossipObserver {
private String name;
public ExcitedFan(String name) {
this.name = name;
}
@Override
public void update(String celebrityName, String scandal) {
System.out.println(name + "兴奋地尖叫:OMG!" + celebrityName + "竟然" + scandal + "!");
System.out.println(" → 立刻打开微博准备吃瓜🍉\n");
}
}
class CalmJournalist implements GossipObserver {
@Override
public void update(String celebrityName, String scandal) {
System.out.println("记者冷静地记录:");
System.out.println(" 【独家爆料】" + celebrityName + "被曝" + scandal);
System.out.println(" → 开始写新闻稿📰\n");
}
}
class IndifferentPerson implements GossipObserver {
@Override
public void update(String celebrityName, String scandal) {
System.out.println("路人甲瞥了一眼:");
System.out.println(" " + celebrityName + "?不认识。关我屁事🙄");
System.out.println(" → 继续刷抖音\n");
}
}
// 3. 主题/被观察者接口(八卦中心)
interface GossipSubject {
void addObserver(GossipObserver observer); // 添加吃瓜群众
void removeObserver(GossipObserver observer); // 移除吃瓜群众
void notifyObservers(); // 发布八卦
}
// 4. 具体主题:明星八卦
class CelebrityScandal implements GossipSubject {
private String celebrityName;
private String scandal;
private List<GossipObserver> observers = new ArrayList<>();
public CelebrityScandal(String name) {
this.celebrityName = name;
}
// 明星有新瓜了!
public void newScandal(String scandal) {
this.scandal = scandal;
System.out.println("⚡⚡⚡ 狗仔队拍到:" + celebrityName + " " + scandal + "!");
System.out.println("八卦开始传播...\n");
notifyObservers(); // 通知所有吃瓜群众
}
@Override
public void addObserver(GossipObserver observer) {
observers.add(observer);
System.out.println("👥 " + observer.getClass().getSimpleName() + " 加入了吃瓜群");
}
@Override
public void removeObserver(GossipObserver observer) {
observers.remove(observer);
System.out.println("🚶 " + observer.getClass().getSimpleName() + " 退群了");
}
@Override
public void notifyObservers() {
for (GossipObserver observer : observers) {
observer.update(celebrityName, scandal);
}
}
}
// 5. 测试:娱乐圈大戏上演
public class ObserverPatternDemo {
public static void main(String[] args) {
System.out.println("========== 娱乐圈观察者模式演示 ==========\n");
// 创建八卦中心(被观察者)
CelebrityScandal wang = new CelebrityScandal("王某");
// 创建不同的吃瓜群众(观察者)
GossipObserver fan1 = new ExcitedFan("狂热粉丝小张");
GossipObserver fan2 = new ExcitedFan("吃瓜少女小李");
GossipObserver journalist = new CalmJournalist();
GossipObserver路人 = new IndifferentPerson();
// 群众陆续加入吃瓜群
wang.addObserver(fan1);
wang.addObserver(fan2);
wang.addObserver(journalist);
wang.addObserver(路人);
System.out.println("\n------- 第一波爆料 -------");
// 第一波爆料
wang.newScandal("深夜与神秘女子同回酒店");
System.out.println("\n------- 粉丝退群后第二波爆料 -------");
// 小张退群了
wang.removeObserver(fan1);
// 第二波爆料
wang.newScandal("被拍到在超市偷吃试吃品");
}
}
运行结果:
========== 娱乐圈观察者模式演示 ==========
👥 ExcitedFan 加入了吃瓜群
👥 ExcitedFan 加入了吃瓜群
👥 CalmJournalist 加入了吃瓜群
👥 IndifferentPerson 加入了吃瓜群
------- 第一波爆料 -------
⚡⚡⚡ 狗仔队拍到:王某 深夜与神秘女子同回酒店!
八卦开始传播...
狂热粉丝小张兴奋地尖叫:OMG!王某竟然深夜与神秘女子同回酒店!
→ 立刻打开微博准备吃瓜🍉
吃瓜少女小李兴奋地尖叫:OMG!王某竟然深夜与神秘女子同回酒店!
→ 立刻打开微博准备吃瓜🍉
记者冷静地记录:
【独家爆料】王某被曝深夜与神秘女子同回酒店
→ 开始写新闻稿📰
路人甲瞥了一眼:
王某?不认识。关我屁事🙄
→ 继续刷抖音
------- 粉丝退群后第二波爆料 -------
🚶 ExcitedFan 退群了
⚡⚡⚡ 狗仔队拍到:王某 被拍到在超市偷吃试吃品!
八卦开始传播...
吃瓜少女小李兴奋地尖叫:OMG!王某竟然被拍到在超市偷吃试吃品!
→ 立刻打开微博准备吃瓜🍉
记者冷静地记录:
【独家爆料】王某被曝被拍到在超市偷吃试吃品
→ 开始写新闻稿📰
路人甲瞥了一眼:
王某?不认识。关我屁事🙄
→ 继续刷抖音
三、观察者模式的四要素
// 1️⃣ 主题 (Subject) - "八卦中心"
// 负责:管理观察者 + 通知变化
interface Subject {
void addObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 2️⃣ 观察者 (Observer) - "吃瓜群众"
// 负责:定义反应方式
interface Observer {
void update(Object data);
}
// 3️⃣ 具体主题 (ConcreteSubject) - "具体明星"
// 负责:状态变化时触发通知
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private Object state; // 状态
public void setState(Object newState) {
this.state = newState;
notifyObservers(); // 状态改变,通知所有人!
}
}
// 4️⃣ 具体观察者 (ConcreteObserver) - "具体粉丝"
// 负责:实现具体的反应逻辑
class ConcreteObserver implements Observer {
@Override
public void update(Object data) {
// 对状态变化的反应
}
}
四、什么时候用观察者模式?
🎯 适用场景(三个字:要通知!)
-
事件驱动系统 - "有事发生,马上报告!"
// GUI按钮点击事件 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("按钮被点了!"); } }); -
消息订阅系统 - "我有新消息,订阅者请查收"
// 新闻订阅 newsPublisher.subscribe(user1); newsPublisher.subscribe(user2); // 新文章发布 → 所有订阅者收到推送 -
数据监控系统 - "数据变了,快更新显示!"
// 股票价格监控 stock.addObserver(new StockDisplay()); // 显示板 stock.addObserver(new AlertSystem()); // 警报系统 stock.addObserver(new TraderBot()); // 自动交易机器人 // 股价变动 → 所有观察者立即行动 -
MVC模式 - "模型变了,视图快刷新"
// Model数据变化 model.setData(newData); // → 自动通知所有关联的View更新界面
📦 实际项目中的例子
// 电商订单状态通知系统
class Order {
private List<OrderObserver> observers = new ArrayList<>();
private String status;
public void setStatus(String newStatus) {
this.status = newStatus;
notifyObservers();
}
public void addObserver(OrderObserver observer) {
observers.add(observer);
}
private void notifyObservers() {
for (OrderObserver observer : observers) {
observer.onOrderStatusChanged(this, status);
}
}
}
// 不同的观察者
class CustomerNotifier implements OrderObserver {
public void onOrderStatusChanged(Order order, String status) {
sendSMS("尊敬的客户,您的订单状态已更新为:" + status);
}
}
class WarehouseSystem implements OrderObserver {
public void onOrderStatusChanged(Order order, String status) {
if ("已支付".equals(status)) {
prepareGoods(order); // 准备发货
}
}
}
class AccountingSystem implements OrderObserver {
public void onOrderStatusChanged(Order order, String status) {
if ("已完成".equals(status)) {
recordRevenue(order); // 记录收入
}
}
}
五、观察者模式的变体
1. 推模型 vs 拉模型
// 推模型:把数据直接推给观察者(常用)
void update(String celebrity, String scandal) {
// 直接拿到所有信息
}
// 拉模型:观察者自己从主题拉取数据
void update(Subject subject) {
String scandal = subject.getScandal(); // 自己获取需要的数据
}
2. Java内置支持(已过时但要知道)
import java.util.Observable; // 主题
import java.util.Observer; // 观察者
// 但注意:Observable是类不是接口,Java 9后已废弃
3. 事件总线(Event Bus) - 观察者模式的升级版
// 更灵活的事件处理
eventBus.register(subscriber); // 注册订阅者
eventBus.post(new OrderEvent()); // 发布事件
// 所有对OrderEvent感兴趣的订阅者都会收到
六、观察者模式的优缺点
👍 优点
- 松耦合:主题不知道观察者的具体细节,只知道接口
- 动态添加:运行时可以随时添加/移除观察者
- 一对多通知:一个变化可以通知多个对象
- 符合开闭原则:新增观察者无需修改主题
👎 缺点
- 内存泄漏风险:如果观察者没正确移除,可能无法被垃圾回收
- 通知顺序不确定:观察者被通知的顺序通常无法保证
- 性能问题:观察者太多时,通知可能变慢
- 循环依赖:观察者之间相互观察可能导致死循环
🚨 注意事项
// 1. 避免在观察者方法中修改主题(可能导致循环或异常)
@Override
public void update(Subject subject) {
// ❌ 错误做法:可能导致递归调用
subject.setState("新状态");
// ✅ 正确做法:只读取不修改
String state = subject.getState();
}
// 2. 考虑异步通知(避免阻塞)
class AsyncSubject extends Subject {
private ExecutorService executor = Executors.newCachedThreadPool();
@Override
protected void notifyObservers() {
for (Observer observer : observers) {
executor.submit(() -> observer.update(data)); // 异步执行
}
}
}
七、与其他模式的关系
| 模式 | 关系 |
|---|---|
| 发布-订阅模式 | 观察者模式的升级版,通过消息队列解耦 |
| 中介者模式 | 都是处理对象间通信,但中介者是集中式管理 |
| 责任链模式 | 都是传递事件,但责任链是链式处理 |
八、生活中的观察者模式
| 场景 | 主题 | 观察者 | 触发时机 |
|---|---|---|---|
| 微信群 | 群主 | 群成员 | 有人发消息 |
| 天气预报 | 气象局 | 手机APP/电视台 | 天气数据更新 |
| 网红直播 | 主播 | 观众 | 开始直播 |
| GitHub仓库 | 代码仓库 | Star的用户 | 有新提交 |
| 电商打折 | 商品 | 收藏的用户 | 价格变化 |
九、总结:一句话记住
观察者模式 = "我有情况,马上通知大家"
- 主题:就是那个"有情况的人"
- 观察者:就是"等着听消息的人"
- 核心思想:解耦 + 自动通知
📝 使用时机判断表
问自己这些问题:
- ✅ 一个对象的状态变化需要通知其他对象吗?
- ✅ 被通知的对象数量不确定或可能变化吗?
- ✅ 不想让这些对象紧密耦合吗?
如果都是"是" → 用观察者模式!
🎯 最佳实践
- 优先用接口:主题和观察者都用接口定义
- 考虑线程安全:多线程环境下要同步
- 避免过度使用:简单的回调可能就够了
- 使用现有框架:Spring事件、Guava EventBus等
记住口诀:"状态变,发通知;观察者,自动动;松耦合,真轻松!" 🎉
❤️ 如果你喜欢这篇文章,请点赞支持! 👍 同时欢迎关注我的博客,获取更多精彩内容!
本文来自博客园,作者:佛祖让我来巡山,转载请注明原文链接:https://www.cnblogs.com/sun-10387834/p/19452781

浙公网安备 33010602011771号