【面试题】什么是观察者模式?一般用在什么场景?

观察者模式:消息界的"八卦小分队" 📢

一、什么是观察者模式?

想象一下微信群聊

  • 你发一条消息(发布事件)
  • 群里所有人都收到了通知(观察者被触发)
  • 有些人回复,有些人点赞,有些人潜水(不同的反应)

这就是观察者模式一个对象(被观察者)的状态变化,会通知所有依赖它的对象(观察者)

二、Java代码示例:明星出轨爆料现场 🎬

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!王某竟然被拍到在超市偷吃试吃品!
    → 立刻打开微博准备吃瓜🍉

记者冷静地记录:
    【独家爆料】王某被曝被拍到在超市偷吃试吃品
    → 开始写新闻稿📰

路人甲瞥了一眼:
    王某?不认识。关我屁事🙄
    → 继续刷抖音

三、观察者模式的四要素

java 复制代码
// 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) {
        // 对状态变化的反应
    }
}

四、什么时候用观察者模式?

🎯 适用场景(三个字:要通知!)

  1. 事件驱动系统 - "有事发生,马上报告!"

    java 复制代码
    // GUI按钮点击事件
    button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("按钮被点了!");
        }
    });
  2. 消息订阅系统 - "我有新消息,订阅者请查收"

    java 复制代码
    // 新闻订阅
    newsPublisher.subscribe(user1);
    newsPublisher.subscribe(user2);
    // 新文章发布 → 所有订阅者收到推送
  3. 数据监控系统 - "数据变了,快更新显示!"

    java 复制代码
    // 股票价格监控
    stock.addObserver(new StockDisplay());  // 显示板
    stock.addObserver(new AlertSystem());   // 警报系统
    stock.addObserver(new TraderBot());     // 自动交易机器人
    // 股价变动 → 所有观察者立即行动
  4. MVC模式 - "模型变了,视图快刷新"

    java 复制代码
    // Model数据变化
    model.setData(newData);  
    // → 自动通知所有关联的View更新界面

📦 实际项目中的例子

java 复制代码
// 电商订单状态通知系统
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 拉模型

java 复制代码
// 推模型:把数据直接推给观察者(常用)
void update(String celebrity, String scandal) {
    // 直接拿到所有信息
}

// 拉模型:观察者自己从主题拉取数据
void update(Subject subject) {
    String scandal = subject.getScandal();  // 自己获取需要的数据
}

2. Java内置支持(已过时但要知道)

java 复制代码
import java.util.Observable;  // 主题
import java.util.Observer;    // 观察者

// 但注意:Observable是类不是接口,Java 9后已废弃

3. 事件总线(Event Bus) - 观察者模式的升级版

java 复制代码
// 更灵活的事件处理
eventBus.register(subscriber);  // 注册订阅者
eventBus.post(new OrderEvent());  // 发布事件
// 所有对OrderEvent感兴趣的订阅者都会收到

六、观察者模式的优缺点

👍 优点

  • 松耦合:主题不知道观察者的具体细节,只知道接口
  • 动态添加:运行时可以随时添加/移除观察者
  • 一对多通知:一个变化可以通知多个对象
  • 符合开闭原则:新增观察者无需修改主题

👎 缺点

  • 内存泄漏风险:如果观察者没正确移除,可能无法被垃圾回收
  • 通知顺序不确定:观察者被通知的顺序通常无法保证
  • 性能问题:观察者太多时,通知可能变慢
  • 循环依赖:观察者之间相互观察可能导致死循环

🚨 注意事项

java 复制代码
// 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的用户 有新提交
电商打折 商品 收藏的用户 价格变化

九、总结:一句话记住

观察者模式 = "我有情况,马上通知大家"

  • 主题:就是那个"有情况的人"
  • 观察者:就是"等着听消息的人"
  • 核心思想解耦 + 自动通知

📝 使用时机判断表

问自己这些问题:

  1. ✅ 一个对象的状态变化需要通知其他对象吗?
  2. ✅ 被通知的对象数量不确定或可能变化吗?
  3. ✅ 不想让这些对象紧密耦合吗?

如果都是"是" → 用观察者模式!

🎯 最佳实践

  1. 优先用接口:主题和观察者都用接口定义
  2. 考虑线程安全:多线程环境下要同步
  3. 避免过度使用:简单的回调可能就够了
  4. 使用现有框架:Spring事件、Guava EventBus等

记住口诀:"状态变,发通知;观察者,自动动;松耦合,真轻松!" 🎉

相关推荐
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——适配器模式
c++·笔记·设计模式
点云SLAM2 小时前
C++ 设计模式之工厂模式(Factory)和面试问题
开发语言·c++·设计模式·面试·c++11·工厂模式
红头辣椒2 小时前
AI赋能全流程,重塑需求管理新生态——Visual RM需求数智化平台核心能力解析
人工智能·设计模式·软件工程·需求分析·用户运营
魅影骑士001017 小时前
柯里化函数
后端·设计模式
BHXDML21 小时前
Java 设计模式详解
java·开发语言·设计模式
Engineer邓祥浩1 天前
设计模式学习(12) 23-10 外观模式
学习·设计模式·外观模式
Geoking.1 天前
【设计模式】享元模式(Flyweight)详解:用共享对象对抗内存爆炸
java·设计模式·享元模式
callJJ1 天前
Spring设计模式与依赖注入详解
java·spring·设计模式·idea·工厂模式
sxlishaobin1 天前
设计模式之组合模式
设计模式·组合模式