观察者模式:一个对象变化,一群对象自动响应

引言

你一定遇到过这样的需求:用户注册成功后,既要"发欢迎邮件",又要"送新人优惠券",还要"记录注册日志",甚至以后可能还要"发短信通知"。如果把这些逻辑全部硬塞在注册方法里:

java

复制代码
public void register(User user) {
    // 保存用户
    saveUser(user);
    // 发邮件
    sendEmail(user);
    // 送券
    sendCoupon(user);
    // 记日志
    writeLog(user);
    // 以后加需求还得回来改这里......
}

每加一个后续动作,都要回来改这个方法,注册逻辑和一堆"副作用"死死耦合在一起。观察者模式(Observer Pattern) ​就是来解决"一个对象状态变化,需要自动通知一群其他对象"这类问题的。它属于行为型设计模式,也是"发布-订阅"机制的鼻祖。

一、什么是观察者模式

观察者模式的核心思想:**定义对象间的一种一对多依赖关系,当一个对象(主题/被观察者)状态改变时,所有依赖它的对象(观察者)都会得到通知并自动更新。**​

两个核心角色:

  • 主题(Subject / Observable):被观察的对象,维护一个观察者列表,提供注册、移除、通知的方法。
  • 观察者(Observer):依赖主题的对象,当主题状态变化时收到通知并作出响应。

打个比方:你关注了一个公众号(订阅),公众号一发文,你和所有其他关注者都会收到推送。公众号是"主题",你们是"观察者"。这就是观察者模式,也就是常说的"发布-订阅"。

二、自定义实现观察者模式

用一个"气象站"的经典例子:气象站(主题)实时更新气温,多个显示面板(观察者)收到通知后各自展示。

2.1 定义观察者接口

java

复制代码
public interface Observer {
    void update(float temperature);
}

2.2 定义主题接口

java

复制代码
public interface Subject {
    void registerObserver(Observer observer);   // 注册观察者
    void removeObserver(Observer observer);     // 移除观察者
    void notifyObservers();                     // 通知所有观察者
}

2.3 实现具体主题(气象站)

java

复制代码
import java.util.ArrayList;
import java.util.List;

public class WeatherStation implements Subject {
    private final List<Observer> observers = new ArrayList<>();
    private float temperature;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature); // 把最新数据推给每个观察者
        }
    }

    // 气温变化时,更新数据并通知所有观察者
    public void setTemperature(float temperature) {
        this.temperature = temperature;
        notifyObservers();
    }
}

2.4 实现具体观察者

java

复制代码
public class PhoneDisplay implements Observer {
    private final String name;

    public PhoneDisplay(String name) {
        this.name = name;
    }

    @Override
    public void update(float temperature) {
        System.out.println(name + " 显示当前气温:" + temperature + "℃");
    }
}

2.5 使用

java

复制代码
public class Main {
    public static void main(String[] args) {
        WeatherStation station = new WeatherStation();

        // 两个面板订阅了气象站
        station.registerObserver(new PhoneDisplay("手机面板"));
        station.registerObserver(new PhoneDisplay("电脑面板"));

        // 气温变化,两个面板自动收到通知
        station.setTemperature(25.5f);
        // 手机面板 显示当前气温:25.5℃
        // 电脑面板 显示当前气温:25.5℃
    }
}

好处 :以后要新增一个"电视面板"观察者,只需 registerObserver 一个新观察者,完全不改气象站和其他观察者的代码,符合开闭原则。

三、观察者模式的两种通知方式

  1. **推模型(Push)**​:主题主动把所有数据"推"给观察者(上面例子就是)。优点是观察者简单,缺点是主题把所有数据都推过去,观察者可能只用到其中一部分。

  2. **拉模型(Pull)**​:主题只通知"我变了",观察者自己按需"拉"取关心的数据。更灵活,但观察者要持有主题的引用。

java

复制代码
// 拉模型:update 不带数据,观察者自己来取
public interface Observer {
    void update();
}
// 观察者里:
@Override
public void update() {
    float temp = weatherStation.getTemperature(); // 自己拉数据
}

实际开发中两种都有,按需选择。

四、实际应用:Spring 事件机制

Spring 提供了一套完整的观察者模式实现------事件监听机制 ,ApplicationEvent + ApplicationListener + @EventListener。回到引言里的"注册后发邮件、送券、记日志",用 Spring 事件可以非常优雅地解耦:

4.1 定义事件

java

复制代码
public class UserRegisterEvent extends ApplicationEvent {
    private final User user;

    public UserRegisterEvent(Object source, User user) {
        super(source);
        this.user = user;
    }

    public User getUser() {
        return user;
    }
}

4.2 发布事件

java

复制代码
@Service
public class UserService {

    @Autowired
    private ApplicationEventPublisher publisher;

    public void register(User user) {
        saveUser(user); // 核心逻辑
        // 只发布事件,不关心谁在监听
        publisher.publishEvent(new UserRegisterEvent(this, user));
    }
}

4.3 多个监听器(观察者)各自处理

java

复制代码
@Component
public class EmailListener {
    @EventListener
    public void onRegister(UserRegisterEvent event) {
        System.out.println("给 " + event.getUser().getName() + " 发送欢迎邮件");
    }
}

@Component
public class CouponListener {
    @EventListener
    public void onRegister(UserRegisterEvent event) {
        System.out.println("给 " + event.getUser().getName() + " 发放新人优惠券");
    }
}

@Component
public class LogListener {
    @EventListener
    public void onRegister(UserRegisterEvent event) {
        System.out.println("记录注册日志:" + event.getUser().getName());
    }
}

现在 register 方法干干净净只做核心业务,发邮件、送券、记日志这些"副作用"分散到了各自的监听器里,新增动作只需加一个监听器,彻底解耦。**这就是观察者模式在 Spring 里的最佳实践。**​

另外,结合 @Async 还能让这些监听器异步执行,进一步提升性能(这点在你前面写过的 AOP、统一处理那几篇里也提到过)。

五、观察者模式 vs 发布-订阅模式

很多人把这两个当成一回事,严格来说有细微差别:

对比项 观察者模式 发布-订阅模式
耦合方式 主题直接持有观察者,二者互相知道对方 通过"事件中心/消息中间件"中转,双方互不知道
通知机制 同步直接调用 通常异步、经消息队列
典型实现 Java Observable、Spring 事件 MQ(RocketMQ、Kafka)、Redis 发布订阅

简单理解:**观察者模式是"点对点"的松耦合,发布-订阅是经过"中间人"的更彻底解耦。**​ 观察者模式可以看作发布-订阅在"单应用内"的简化版。

总结

观察者模式是实现"一对多自动通知、模块解耦"的利器:

  1. 核心:主题维护观察者列表,状态变化时逐一通知;观察者自动响应。
  2. 两种方式:推模型(主题推数据)和拉模型(观察者自己取)。
  3. Spring 事件机制 是观察者模式的最佳实践------ApplicationEvent + @EventListener,把核心业务和"邮件、优惠券、日志"等副作用彻底解耦,新增功能只加监听器不动主流程。
  4. 与发布-订阅的区别:观察者是单应用内的直接通知,发布-订阅经过消息中间件中转、更彻底解耦。

一句话总结:当一个对象变化需要带动一群对象响应时,别把逻辑都堆在变化的方法里,用观察者模式把"谁响应、怎么响应"分离出去。

相关推荐
小张成长计划..44 分钟前
【Linux】26:线程互斥和同步
java·开发语言
邪修king3 小时前
Re:Linux 系统篇(三十五):进程间通信Chapter2:从匿名管道到进程池:Linux 进程间通信实战(深度理解「先描述,再组织」)
java·linux·运维
拆房老料4 小时前
【第四部分:Agentic 智能体设计模式】16. 一个 Prompt 搞不定复杂任务?试试 Prompt Chaining
java·前端·人工智能·设计模式·prompt
张张123y5 小时前
后端第十四课:Agent 记忆系统(让 Agent 记住上下文和用户,跨会话不健忘)
java·大数据·人工智能·python·agent·记忆
曹牧9 小时前
C#:空合并运算符
java·前端·数据库
布吉岛的石头9 小时前
Java 程序员第 50 阶段2:预训练的目标函数:自回归语言建模本质
java·开发语言·人工智能·深度学习·回归·transformer
golang学习记10 小时前
IDEA 2026年的玩法已经完全不同
java·ide·intellij-idea
紧跟先前的步伐10 小时前
Go AI Agent 实战 05|Temperature 与生成参数:让模型输出可控
java·人工智能·golang
金銀銅鐵10 小时前
[Java] 展示添加元素时 ArrayList 的内部变化
java·后端·ai编程