15、Java 观察者模式从入门到实战

Java 观察者模式从入门到实战(后端必看,附案例+面试考点)

前言:观察者模式(Observer Pattern)是 Java 行为型设计模式中的核心模式,也被叫做发布 - 订阅模式 ,完美解决了对象间一对多依赖、一个对象状态变更自动通知其他依赖对象的场景问题。

日常开发中,消息推送、事件监听、缓存更新、订单状态同步等场景,都离不开观察者模式;Spring 的事件驱动、RabbitMQ 消息队列、Guava EventBus 等技术,底层也都基于观察者模式思想实现。

本文从痛点问题、核心概念、入门代码、真实业务实战、框架应用、高频面试题全维度拆解,带你彻底吃透观察者模式,新手能直接上手,老手能落地项目,面试轻松拿高分。

一、为什么要学观察者模式?(痛点直击)

先看后端开发中高频遇到的问题,不用观察者模式,代码会变得极其臃肿、耦合严重:

  1. 订单支付成功后:需要同步更新订单状态、推送支付成功消息、增加用户积分、扣减商品库存,所有逻辑写在一个方法里,代码冗长,新增 / 删除逻辑需修改原有代码;

  2. 用户注册成功后:需要发送注册短信、初始化用户账户、记录注册日志,业务逻辑高度耦合,违背开闭原则;

  3. 状态变更通知:一个对象状态改变,多个对象需要感知并执行操作,硬编码调用导致代码难以维护、扩展性极差。

这些问题的核心:对象间强耦合,状态变更无法自动通知,业务逻辑耦合在一起,维护和扩展成本极高

观察者模式 ,就是专门解决这类一对多依赖、状态自动通知、业务解耦的问题,将发布者和订阅者完全分离,发布者只负责发布消息,订阅者只负责接收并处理消息,新增订阅逻辑无需修改发布者代码,完美符合开闭原则。

简单理解:观察者模式就像公众号订阅,公众号(被观察者 / 主题)发布文章,所有关注的用户(观察者)都会收到推送,用户可随时订阅 / 取消订阅,公众号无需关心有多少用户关注。

二、观察者模式核心概念与角色

2.1 模式定义

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

2.2 核心角色(5 大核心,必记)

  1. 抽象主题(Subject):也叫抽象被观察者,定义添加、删除、通知观察者的接口,持有观察者集合;

  2. 具体主题(ConcreteSubject):实现抽象主题接口,维护自身状态,状态变更时通知所有观察者;

  3. 抽象观察者(Observer):定义收到通知后的更新方法,所有具体观察者必须实现;

  4. 具体观察者(ConcreteObserver):实现抽象观察者接口,接收到通知后执行业务逻辑;

  5. 客户端(Client):创建主题和观察者对象,将观察者注册到主题中,触发主题状态变更。

2.3 核心执行流程

  1. 客户端创建主题对象和多个观察者对象;

  2. 将观察者注册到主题的观察者列表中;

  3. 主题状态发生变更,遍历观察者列表,调用每个观察者的更新方法;

  4. 观察者接收通知,执行自身业务逻辑。

三、观察者模式入门实现(原生 Java,可直接复制)

订单支付通知为例,实现原生观察者模式,模拟支付成功后,消息服务、积分服务、库存服务自动执行逻辑。

3.1 抽象观察者(Observer)

java 复制代码
/**
 * 抽象观察者
 */
public interface OrderObserver {
    /**
     * 订单状态更新通知
     * @param orderId 订单ID
     */
    void update(String orderId);
}

3.2 具体观察者(多个)

java 复制代码
/**
 * 消息服务观察者:发送支付成功短信
 */
@Component
public class MessageObserver implements OrderObserver {
    @Override
    public void update(String orderId) {
        System.out.println("【消息服务】订单:" + orderId + " 支付成功,已发送短信通知");
    }
}

/**
 * 积分服务观察者:增加用户积分
 */
@Component
public class IntegralObserver implements OrderObserver {
    @Override
    public void update(String orderId) {
        System.out.println("【积分服务】订单:" + orderId + " 支付成功,已增加用户积分");
    }
}

/**
 * 库存服务观察者:扣减商品库存
 */
@Component
public class StockObserver implements OrderObserver {
    @Override
    public void update(String orderId) {
        System.out.println("【库存服务】订单:" + orderId + " 支付成功,已扣减商品库存");
    }
}

3.3 抽象主题(Subject)

java 复制代码
/**
 * 抽象主题(被观察者)
 */
public interface OrderSubject {
    // 添加观察者
    void addObserver(OrderObserver observer);
    // 删除观察者
    void removeObserver(OrderObserver observer);
    // 通知所有观察者
    void notifyObservers(String orderId);
}

3.4 具体主题

java 复制代码
/**
 * 订单主题:具体被观察者
 */
@Component
public class OrderConcreteSubject implements OrderSubject {

    // 观察者集合
    private List<OrderObserver> observerList<>();

    @Override
    public void addObserver(OrderObserver observer) {
        observerList.add(observer);
    }

    @Override
    public void removeObserver(OrderObserver observer) {
        observerList.remove(observer);
    }

    @Override
    public void notifyObservers(String orderId) {
        // 遍历通知所有观察者
        for (OrderObserver observer : observerList) {
            observer.update(orderId);
        }
    }
}

3.5 客户端测试

java 复制代码
public class ObserverTest {
    public static void main(String[] args) {
        // 1. 创建主题(被观察者)
        OrderSubject orderSubject = new OrderConcreteSubject();

        // 2. 创建观察者并注册到主题
        OrderObserver messageObserver = new MessageObserver();
        OrderObserver integralObserver = new IntegralObserver();
        OrderObserver stockObserver = new StockObserver();

        orderSubject.addObserver(messageObserver);
        orderSubject.addObserver(integralObserver);
        orderSubject.addObserver(stockObserver);

        // 3. 模拟订单支付成功,通知所有观察者
        System.out.println("订单:ORDER_20240520_001 支付成功");
        orderSubject.notifyObservers("ORDER_20240520_001");
    }
}

3.6 运行结果

Plain 复制代码
订单:ORDER_20240520_001 支付成功
【消息服务】订单:ORDER_20240520_001 支付成功,已发送短信通知
【积分服务】订单:ORDER_20240520_001 支付成功,已增加用户积分
【库存服务】订单:ORDER_20240520_001 支付成功,已扣减商品库存

3.7 代码优势

  1. 解耦彻底:发布者(订单主题)和订阅者(各服务)完全分离,互不依赖;

  2. 扩展性强:新增 / 删除业务逻辑,只需新增 / 删除观察者,无需修改主题代码;

  3. 职责单一:每个观察者只负责自身业务逻辑,符合单一职责原则。

四、Java 原生观察者模式(内置 API)

Java 本身提供了java\.util\.Observable(抽象主题)和java\.util\.Observer(抽象观察者)接口,可直接使用,无需自定义接口。

4.1 具体主题(继承 Observable)

java 复制代码
import java.util.Observable;

/**
 * Java原生订单主题
 */
public class JavaOrderSubject extends Observable {
    // 订单支付成功,触发通知
    public void paySuccess(String orderId) {
        // 标记状态已变更
        setChanged();
        // 通知所有观察者
        notifyObservers(orderId);
    }
}

4.2 具体观察者(实现 Observer)

java 复制代码
import java.util.Observable;
import java.util.Observer;

/**
 * 原生消息观察者
 */
public class JavaMessageObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        String orderId = (String) arg;
        System.out.println("【原生消息服务】订单:" + orderId + " 支付成功,已发送通知");
    }
}

注意:Java 内置的 Observable 是类,而非接口,存在单继承局限,实际开发中更推荐自定义接口实现。

五、真实业务实战(Spring Boot + 观察者模式)

实际后端开发中,结合 Spring Boot 实现观察者模式,通过依赖注入管理对象,适配真实业务场景,代码可直接复用。

5.1 实战场景

用户注册成功后,自动执行:

  1. 发送注册成功短信;

  2. 初始化用户个人账户;

  3. 记录用户注册日志。

5.2 核心代码实现

1. 抽象观察者
java 复制代码
public interface UserRegisterObserver {
    void handleRegisterSuccess(Long userId);
}
2. 具体观察者
java 复制代码
// 短信服务观察者
@Service
public class SmsObserver implements UserRegisterObserver {
    @Override
    public void handleRegisterSuccess(Long userId) {
        System.out.println("【短信服务】用户ID:" + userId + " 注册成功,发送短信通知");
    }
}

// 账户初始化观察者
@Service
public class AccountObserver implements UserRegisterObserver {
    @Override
    public void handleRegisterSuccess(Long userId) {
        System.out.println("【账户服务】用户ID:" + userId + " 注册成功,初始化个人账户");
    }
}

// 日志记录观察者
@Service
public class LogObserver implements UserRegisterObserver {
    @Override
    public void handleRegisterSuccess(Long userId) {
        System.out.println("【日志服务】用户ID:" + userId + " 注册成功,记录注册日志");
    }
}
3. 主题(被观察者)
java 复制代码
@Service
public class UserRegisterSubject {

    @<UserRegisterObserver> registerObservers;

    // 用户注册成功,通知所有观察者
    public void registerSuccess(Long userId) {
        System.out.println("用户ID:" + userId + " 注册成功");
        for (UserRegisterObserver observer : registerObservers) {
            observer.handleRegisterSuccess(userId);
        }
    }
}
4. 控制层调用
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserRegisterSubject userRegisterSubject;

    @PostMapping("/register")
    public String register(Long userId) {
        // 模拟用户注册逻辑
        userRegisterSubject.registerSuccess(userId);
        return "注册成功";
    }
}

5.3 实战亮点

  1. 完全适配 Spring Boot 开发,通过 @Autowired 自动注入所有观察者;

  2. 无硬编码,新增业务只需新增观察者类,无需修改原有代码;

  3. 业务解耦,便于后续维护和扩展。

六、观察者模式在主流框架中的应用

6.1 Spring 事件驱动模型

Spring 的ApplicationEvent(事件)、ApplicationListener(事件监听),就是观察者模式的经典实现:

  • ApplicationEvent:抽象主题;

  • ApplicationListener:抽象观察者;

  • ApplicationContext:事件发布者,负责通知所有监听器。

6.2 Guava EventBus

Google Guava 提供的 EventBus,是观察者模式的优雅实现,简化了事件注册和通知,无需手动维护观察者列表。

6.3 RabbitMQ/RocketMQ 消息队列

消息队列的发布 - 订阅模式,本质是分布式的观察者模式,生产者(发布者)发送消息,消费者(订阅者)监听并消费消息,实现跨服务的解耦通知。

6.4 Swing/AWT 事件监听

Java 桌面开发中的按钮点击、鼠标移动等事件,都是基于观察者模式实现,组件是被观察者,事件监听器是观察者。

七、观察者模式高频面试考点(必背)

1. 观察者模式和发布 - 订阅模式的区别?

经典面试题,必背

  • 观察者模式:一对多,直接耦合,主题和观察者相互感知,在同一进程内;

  • 发布 - 订阅模式:多对多,完全解耦,通过中间件(消息队列、事件总线)通信,发布者和订阅者互不感知,可跨进程 / 跨服务。

2. 观察者模式的优缺点?

优点

  • 解耦:发布者和订阅者分离,互不依赖;

  • 可扩展:新增订阅逻辑无需修改原有代码;

  • 自动通知:状态变更自动通知所有依赖对象。

缺点

  • 观察者过多时,通知效率降低;

  • 观察者之间无依赖,无法感知相互执行顺序;

  • 若观察者和主题循环依赖,可能导致系统崩溃。

3. 观察者模式和责任链模式的区别?

  • 观察者模式:一对多,同时通知所有观察者

  • 责任链模式:一对一,依次传递请求,一个请求被一个处理器处理。

4. 什么场景适合用观察者模式?

  • 一个对象状态变更,需要通知其他多个对象;

  • 业务解耦,拆分耦合的业务逻辑;

  • 事件监听、消息推送、状态同步场景。

八、总结

观察者模式是后端开发必掌握的解耦神器 ,核心思想就是发布 - 订阅,彻底解决对象间一对多的依赖耦合问题,让代码更易维护、更易扩展。

新手学习从原生代码入手,理解核心角色和执行流程;实战开发结合 Spring Boot、EventBus 等工具,落地真实业务;面试重点掌握和发布 - 订阅模式的区别、优缺点、适用场景。

实际开发中,小场景用自定义观察者模式,大场景 / 分布式场景用消息队列,灵活选择,让代码更优雅、更健壮。

相关推荐
CLX050526 分钟前
golang如何实现群聊功能_golang群聊功能实现策略.txt
jvm·数据库·python
zadyd1 小时前
vLLM Linux 双卡部署大模型服务器指南
linux·人工智能·python·机器学习·vllm
紫小米8 小时前
后端日志管理
python·fastapi
你不是我我8 小时前
【Java 开发日记】HTTP3 性能更好,为什么内网微服务依然多用 HTTP2?HTTP2 内网优势是什么?
java·开发语言·微服务
雪碧聊技术8 小时前
大模型爆火!Java后端如何抓住Agent全栈开发的风口
java·大模型·agent·全栈开发
白雪茫茫9 小时前
监督学习、半监督学习、无监督学习算法详解
python·学习·算法·ai
つ安静与叛逆的小籹人9 小时前
小红书API:通过笔记ID获取笔记详情数据教程
笔记·python
05候补工程师9 小时前
[实战复盘] 拒绝 AI 屎山!我从设计模式中学到的“调教”AI 新范式
人工智能·python·设计模式·ai·ai编程
逻辑驱动的ken9 小时前
Java高频面试场景题25
java·开发语言·深度学习·面试·职场和发展
AI人工智能+电脑小能手10 小时前
【大白话说Java面试题】【Java基础篇】第32题:Java的异常处理机制是什么
java·开发语言·后端·面试