【从零入门23种设计模式19】行为型之观察者模式

一、观察者模式核心定义

观察者模式是行为型设计模式的一种,核心目的是:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。

简单来说:一个 "主题"(被观察者)管理所有依赖它的 "观察者",主题状态变化时主动通知所有观察者,观察者收到通知后执行自己的逻辑

核心解决的问题
  1. 解耦通知方与接收方:主题无需知道观察者的具体实现,只需维护观察者列表;观察者无需主动轮询主题状态,等待主题通知即可;
  2. 支持一对多联动:一个主题的状态变化可触发多个观察者的行为(如订单支付成功后,通知库存扣减、积分增加、日志记录);
  3. 动态扩展观察者:新增 / 移除观察者无需修改主题代码,符合开闭原则;
  4. 事件驱动编程:基于 "发布 - 订阅" 模型实现松耦合的事件处理。
生活类比
  • 场景 1 :微信公众号
    • 主题(被观察者):公众号;
    • 观察者:关注公众号的用户;
    • 核心:公众号发布新文章(状态变化),所有关注的用户都会收到推送通知,用户可选择查看 / 忽略。
  • 场景 2 :股票行情
    • 主题:股票(如茅台);
    • 观察者:股民、券商 APP、财经网站;
    • 核心:股票价格变动(状态变化),所有关注该股票的观察者都会收到价格更新通知。
  • 场景 3 :报警系统
    • 主题:温度传感器;
    • 观察者:报警器、监控大屏、运维短信服务;
    • 核心:温度超过阈值(状态变化),传感器通知所有观察者,报警器响、大屏告警、短信推送。
标准角色
角色 职责 类比(公众号场景) JDK 对应类 / 接口
主题 / 被观察者(Subject) 定义注册 / 移除观察者的方法,维护观察者列表;状态变化时通知所有观察者 公众号(管理关注者、推送文章) java.util.Observable
具体主题(ConcreteSubject) 实现主题接口,存储状态;状态变化时触发通知逻辑 具体的公众号(如 "Java 编程思想") 自定义业务主题类
观察者(Observer) 定义接收通知的统一接口(如update()方法) 公众号用户(接收推送的规范) java.util.Observer
具体观察者(ConcreteObserver) 实现观察者接口,收到通知后执行具体业务逻辑 具体的用户(张三、李四) 自定义业务观察者类
核心 UML 类图

二、公众号推送

以 "微信公众号推送" 为例,实现观察者模式的核心逻辑 ------ 覆盖所有核心角色,体现 "一对多" 的通知机制。

1. 步骤 1:定义观察者接口(公众号用户)
复制代码
/**
 * 观察者接口:公众号用户(定义接收推送的规范)
 */
public interface WeChatObserver {
    /**
     * 接收公众号推送的通知
     * @param account 公众号名称
     * @param content 推送内容
     */
    void update(String account, String content);
}
2. 步骤 2:定义主题接口(公众号)
复制代码
/**
 * 主题接口:公众号(定义注册/移除观察者、推送消息的规范)
 */
public interface WeChatSubject {
    /**
     * 注册观察者(用户关注公众号)
     */
    void registerObserver(WeChatObserver observer);

    /**
     * 移除观察者(用户取消关注)
     */
    void removeObserver(WeChatObserver observer);

    /**
     * 通知所有观察者(推送消息)
     */
    void notifyObservers();

    /**
     * 发布新文章(修改主题状态)
     */
    void publishArticle(String content);
}
3. 步骤 3:实现具体主题(Java 编程公众号)
复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 具体主题:Java编程公众号(管理观察者、状态变化时通知)
 */
public class JavaProgramSubject implements WeChatSubject {
    // 公众号名称
    private final String accountName = "Java编程思想";
    // 当前推送的文章内容(主题状态)
    private String articleContent;
    // 观察者列表(关注的用户)
    private final List<WeChatObserver> observers = new ArrayList<>();

    /**
     * 注册观察者(用户关注)
     */
    @Override
    public void registerObserver(WeChatObserver observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
            System.out.println("【主题-公众号】用户关注:" + observer.getClass().getSimpleName());
        }
    }

    /**
     * 移除观察者(用户取消关注)
     */
    @Override
    public void removeObserver(WeChatObserver observer) {
        if (observers.contains(observer)) {
            observers.remove(observer);
            System.out.println("【主题-公众号】用户取消关注:" + observer.getClass().getSimpleName());
        }
    }

    /**
     * 通知所有观察者(核心:遍历观察者列表,调用update方法)
     */
    @Override
    public void notifyObservers() {
        System.out.println("\n【主题-公众号】" + accountName + " 开始推送新文章:" + articleContent);
        for (WeChatObserver observer : observers) {
            observer.update(accountName, articleContent);
        }
    }

    /**
     * 发布新文章(修改状态后触发通知)
     */
    @Override
    public void publishArticle(String content) {
        this.articleContent = content;
        // 状态变化,通知所有观察者
        notifyObservers();
    }
}
4. 步骤 4:实现具体观察者(不同类型用户)
复制代码
/**
 * 具体观察者1:普通用户(接收推送后仅查看)
 */
public class NormalUserObserver implements WeChatObserver {
    private final String username;

    public NormalUserObserver(String username) {
        this.username = username;
    }

    /**
     * 接收推送通知(执行具体业务逻辑)
     */
    @Override
    public void update(String account, String content) {
        System.out.println("【观察者-普通用户】" + username + " 收到 " + account + " 的推送:" + content + " → 已查看");
    }
}

/**
 * 具体观察者2:VIP用户(接收推送后收藏+点赞)
 */
public class VipUserObserver implements WeChatObserver {
    private final String username;

    public VipUserObserver(String username) {
        this.username = username;
    }

    @Override
    public void update(String account, String content) {
        System.out.println("【观察者-VIP用户】" + username + " 收到 " + account + " 的推送:" + content + " → 已收藏+点赞");
    }
}

/**
 * 具体观察者3:运营监控(接收推送后统计阅读量)
 */
public class MonitorObserver implements WeChatObserver {
    private int readCount = 0; // 累计阅读量

    @Override
    public void update(String account, String content) {
        readCount++;
        System.out.println("【观察者-运营监控】收到 " + account + " 的推送 → 累计阅读量:" + readCount);
    }

    public int getReadCount() {
        return readCount;
    }
}
5. 客户端(测试公众号推送)
复制代码
/**
 * 客户端:测试公众号观察者模式
 */
public class ObserverClient {
    public static void main(String[] args) {
        // 1. 创建主题(Java编程公众号)
        WeChatSubject javaSubject = new JavaProgramSubject();

        // 2. 创建观察者(不同类型用户)
        WeChatObserver user1 = new NormalUserObserver("张三");
        WeChatObserver user2 = new VipUserObserver("李四");
        WeChatObserver monitor = new MonitorObserver();

        // 3. 注册观察者(用户关注)
        javaSubject.registerObserver(user1);
        javaSubject.registerObserver(user2);
        javaSubject.registerObserver(monitor);

        // 4. 发布第一篇文章(状态变化,通知所有观察者)
        javaSubject.publishArticle("《设计模式之观察者模式详解》");

        // 5. 发布第二篇文章
        javaSubject.publishArticle("《Spring Boot实战教程》");

        // 6. 移除一个观察者(用户取消关注)
        javaSubject.removeObserver(user1);
        System.out.println();

        // 7. 发布第三篇文章(仅通知剩余观察者)
        javaSubject.publishArticle("《Java并发编程实战》");

        // 8. 打印监控数据
        System.out.println("\n【运营数据】累计阅读量:" + ((MonitorObserver) monitor).getReadCount());
    }
}
输出结果
复制代码
【主题-公众号】用户关注:NormalUserObserver
【主题-公众号】用户关注:VipUserObserver
【主题-公众号】用户关注:MonitorObserver

【主题-公众号】Java编程思想 开始推送新文章:《设计模式之观察者模式详解》
【观察者-普通用户】张三 收到 Java编程思想 的推送:《设计模式之观察者模式详解》 → 已查看
【观察者-VIP用户】李四 收到 Java编程思想 的推送:《设计模式之观察者模式详解》 → 已收藏+点赞
【观察者-运营监控】收到 Java编程思想 的推送 → 累计阅读量:1

【主题-公众号】Java编程思想 开始推送新文章:《Spring Boot实战教程》
【观察者-普通用户】张三 收到 Java编程思想 的推送:《Spring Boot实战教程》 → 已查看
【观察者-VIP用户】李四 收到 Java编程思想 的推送:《Spring Boot实战教程》 → 已收藏+点赞
【观察者-运营监控】收到 Java编程思想 的推送 → 累计阅读量:2

【主题-公众号】用户取消关注:NormalUserObserver

【主题-公众号】Java编程思想 开始推送新文章:《Java并发编程实战》
【观察者-VIP用户】李四 收到 Java编程思想 的推送:《Java并发编程实战》 → 已收藏+点赞
【观察者-运营监控】收到 Java编程思想 的推送 → 累计阅读量:3

【运营数据】累计阅读量:3

三、Spring 实战(订单支付事件通知)

在业务开发中,观察者模式最核心的实战场景是订单支付后的多系统联动 (如支付成功后,通知库存扣减、积分增加、日志记录、消息推送)。以下基于 Spring 的ApplicationEventApplicationListener实现观察者模式,这是 Spring 中最原生的事件通知方式。

1. 依赖准备(Spring Boot)
复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>3.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
2. 核心模型定义
复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.ApplicationEvent;

/**
 * 订单支付事件(主题状态:封装支付成功的订单信息)
 * 核心:继承ApplicationEvent,成为Spring可识别的事件(主题)
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderPayEvent extends ApplicationEvent {
    private String orderId; // 订单ID
    private String userId; // 用户ID
    private double amount; // 支付金额
    private String payTime; // 支付时间

    /**
     * 构造函数必须调用父类构造(source为事件源)
     */
    public OrderPayEvent(Object source, String orderId, String userId, double amount, String payTime) {
        super(source);
        this.orderId = orderId;
        this.userId = userId;
        this.amount = amount;
        this.payTime = payTime;
    }
}

/**
 * 订单DTO(业务传输对象)
 */
@Data
@AllArgsConstructor
public class OrderDTO {
    private String orderId;
    private String userId;
    private double amount;
}
3. 具体观察者(事件监听器)
复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 具体观察者1:库存监听器(支付成功后扣减库存)
 * 核心:实现ApplicationListener,监听OrderPayEvent
 */
@Slf4j
@Component
public class StockListener implements ApplicationListener<OrderPayEvent> {
    /**
     * 收到支付事件后执行扣减库存逻辑
     */
    @Override
    public void onApplicationEvent(OrderPayEvent event) {
        log.info("【观察者-库存监听器】订单支付成功,开始扣减库存 | 订单ID:{},用户ID:{}",
                event.getOrderId(), event.getUserId());
        // 模拟扣减库存逻辑
        log.info("【观察者-库存监听器】库存扣减成功 | 订单ID:{}", event.getOrderId());
    }
}

/**
 * 具体观察者2:积分监听器(支付成功后增加用户积分)
 */
@Slf4j
@Component
public class PointListener implements ApplicationListener<OrderPayEvent> {
    @Override
    public void onApplicationEvent(OrderPayEvent event) {
        log.info("【观察者-积分监听器】订单支付成功,开始增加积分 | 用户ID:{},支付金额:{}",
                event.getUserId(), event.getAmount());
        // 模拟增加积分:1元=1积分
        int point = (int) event.getAmount();
        log.info("【观察者-积分监听器】积分增加成功 | 用户ID:{},增加积分:{}", event.getUserId(), point);
    }
}

/**
 * 具体观察者3:日志监听器(支付成功后记录操作日志)
 */
@Slf4j
@Component
public class LogListener implements ApplicationListener<OrderPayEvent> {
    @Override
    public void onApplicationEvent(OrderPayEvent event) {
        log.info("【观察者-日志监听器】记录订单支付日志 | 订单ID:{},支付时间:{},金额:{}",
                event.getOrderId(), event.getPayTime(), event.getAmount());
        // 模拟日志入库逻辑
        log.info("【观察者-日志监听器】日志记录成功 | 订单ID:{}", event.getOrderId());
    }
}

/**
 * 具体观察者4:消息推送监听器(支付成功后推送短信)
 */
@Slf4j
@Component
public class MessageListener implements ApplicationListener<OrderPayEvent> {
    @Override
    public void onApplicationEvent(OrderPayEvent event) {
        log.info("【观察者-消息监听器】订单支付成功,开始推送短信 | 用户ID:{},订单ID:{}",
                event.getUserId(), event.getOrderId());
        // 模拟短信推送逻辑
        log.info("【观察者-消息监听器】短信推送成功 | 用户ID:{},内容:您的订单{}已支付成功,金额{}元",
                event.getUserId(), event.getOrderId(), event.getAmount());
    }
}
4. 主题发布者(订单服务)
复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 主题发布者:订单服务(支付成功后发布事件)
 * 核心:通过ApplicationContext发布事件(通知所有观察者)
 */
@Slf4j
@Service
public class OrderService {
    // Spring上下文:用于发布事件(替代手动维护观察者列表)
    @Resource
    private ApplicationContext applicationContext;

    /**
     * 处理订单支付(支付成功后发布事件)
     */
    public boolean payOrder(OrderDTO orderDTO) {
        log.info("【主题-订单服务】开始处理订单支付 | 订单ID:{},用户ID:{},金额:{}",
                orderDTO.getOrderId(), orderDTO.getUserId(), orderDTO.getAmount());

        // 模拟支付逻辑(此处简化,直接返回成功)
        boolean paySuccess = true;
        if (paySuccess) {
            // 1. 生成支付时间
            String payTime = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
            log.info("【主题-订单服务】订单支付成功 | 订单ID:{},支付时间:{}", orderDTO.getOrderId(), payTime);

            // 2. 发布支付事件(核心:通知所有观察者)
            OrderPayEvent payEvent = new OrderPayEvent(
                    this, // 事件源(当前服务)
                    orderDTO.getOrderId(),
                    orderDTO.getUserId(),
                    orderDTO.getAmount(),
                    payTime
            );
            applicationContext.publishEvent(payEvent);
            log.info("【主题-订单服务】支付事件发布完成 | 订单ID:{}", orderDTO.getOrderId());
        }

        return paySuccess;
    }
}
5. 客户端(Spring Boot 测试)
复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * 客户端:测试订单支付事件通知
 */
@SpringBootApplication
public class SpringObserverDemoApplication {
    public static void main(String[] args) {
        // 1. 启动Spring容器(自动注册所有监听器)
        ConfigurableApplicationContext context = SpringApplication.run(SpringObserverDemoApplication.class, args);
        OrderService orderService = context.getBean(OrderService.class);

        // 2. 测试订单支付(触发事件发布)
        System.out.println("======= 测试订单支付事件通知 =======");
        OrderDTO order = new OrderDTO("O20240313001", "U1001", 999.9);
        boolean payResult = orderService.payOrder(order);
        System.out.println("订单支付结果:" + payResult);

        context.close();
    }
}
输出结果
复制代码
======= 测试订单支付事件通知 =======
【主题-订单服务】开始处理订单支付 | 订单ID:O20240313001,用户ID:U1001,金额:999.9
【主题-订单服务】订单支付成功 | 订单ID:O20240313001,支付时间:2024-03-13T15:30:00.123456
【观察者-库存监听器】订单支付成功,开始扣减库存 | 订单ID:O20240313001,用户ID:U1001
【观察者-库存监听器】库存扣减成功 | 订单ID:O20240313001
【观察者-积分监听器】订单支付成功,开始增加积分 | 用户ID:U1001,支付金额:999.9
【观察者-积分监听器】积分增加成功 | 用户ID:U1001,增加积分:999
【观察者-日志监听器】记录订单支付日志 | 订单ID:O20240313001,支付时间:2024-03-13T15:30:00.123456,金额:999.9
【观察者-日志监听器】日志记录成功 | 订单ID:O20240313001
【观察者-消息监听器】订单支付成功,开始推送短信 | 用户ID:U1001,订单ID:O20240313001
【观察者-消息监听器】短信推送成功 | 用户ID:U1001,内容:您的订单O20240313001已支付成功,金额999.9元
【主题-订单服务】支付事件发布完成 | 订单ID:O20240313001
订单支付结果:true

四、观察者模式的核心特点与适用场景

优点
  1. 解耦主题与观察者:主题无需知道观察者的具体实现,只需维护观察者列表;观察者无需知道主题的内部逻辑,只需处理通知;
  2. 支持动态扩展:新增 / 移除观察者无需修改主题代码,符合开闭原则;
  3. 一对多联动:一个主题的状态变化可触发多个观察者的行为,实现复杂的业务联动;
  4. 事件驱动:基于 "发布 - 订阅" 模型,实现异步 / 同步的事件处理;
  5. 简化依赖关系:避免主题与多个观察者之间的直接耦合,降低系统复杂度。
缺点
  1. 通知顺序不可控:默认情况下,观察者的通知顺序由主题的观察者列表顺序决定,复杂场景下难以保证执行顺序;
  2. 内存泄漏风险:如果观察者未正确移除(如匿名内部类),会导致主题持有观察者引用,造成内存泄漏;
  3. 性能损耗:观察者数量过多时,通知所有观察者会消耗大量时间;
  4. 异常传播:一个观察者的异常可能导致后续观察者无法收到通知(同步通知场景);
  5. 调试复杂:事件通知是隐式调用,定位问题时需跟踪整个通知链路。
适用场景
  1. 事件通知:订单状态变更、支付结果通知、用户注册成功后的多系统联动;
  2. 状态监控:系统监控(CPU / 内存 / 磁盘)、设备状态监控(传感器、物联网设备);
  3. 消息推送:公众号推送、短信推送、APP 消息推送;
  4. GUI 交互:按钮点击、文本框输入、下拉框选择等 UI 事件处理;
  5. 分布式系统:消息队列(Kafka/RabbitMQ)、微服务事件通知(Spring Cloud Event);
  6. 日志 / 监控:系统日志收集、操作审计、性能监控。

五、JDK/ Spring 中的原生应用(必须知道)

观察者模式是框架中最常用的设计模式之一,以下是核心场景:

1. JDK 核心观察者
  • java.util.Observable :主题抽象类(已标记为过时,推荐使用自定义接口);
  • java.util.Observer :观察者接口;
  • java.beans.PropertyChangeListener :属性变更监听器(JavaBean 的属性变化通知);
  • java.awt.event.EventListener :AWT/Swing 的事件监听器(如按钮点击、鼠标移动)。
2. Spring 核心观察者
  • ApplicationEvent :Spring 事件基类(主题);
  • ApplicationListener :Spring 事件监听器(观察者);
  • ApplicationEventPublisher :事件发布器(主题的发布接口);
  • @EventListener :注解式监听器(Spring 4.2+,替代实现 ApplicationListener);
  • SmartApplicationListener :智能监听器(支持指定事件类型、控制执行顺序)。
3. 消息队列(Kafka/RabbitMQ)
  • 主题:Topic/Exchange;
  • 观察者:Consumer;
  • 核心逻辑:生产者发布消息到主题,所有订阅该主题的消费者都会收到消息(分布式观察者模式)。
4. Android 事件系统
  • 主题:View(按钮、文本框);
  • 观察者:OnClickListener/OnTextChangedListener;
  • 核心逻辑:View 状态变化时通知所有注册的监听器。
5. MyBatis 插件
  • 主题:MyBatis 的执行流程(SQL 执行、参数处理);
  • 观察者:Interceptor(拦截器);
  • 核心逻辑:MyBatis 执行 SQL 时通知所有拦截器,拦截器可修改执行流程。

六、观察者模式 vs 发布 - 订阅模式(易混淆点)

很多人会混淆观察者模式和发布 - 订阅模式,其实发布 - 订阅是观察者模式的升级版

维度 经典观察者模式 发布 - 订阅模式
核心结构 主题直接持有观察者列表,直接通知 引入 "消息中间件 / 事件总线",主题和观察者解耦
耦合度 主题与观察者轻度耦合(直接引用) 完全解耦(主题 / 观察者只依赖中间件)
通信方式 同步通信(默认) 异步通信(主流)
适用场景 单机内的事件通知 分布式系统的跨服务通知
典型实现 JDK Observable、Spring ApplicationEvent Kafka/RabbitMQ、EventBus

总结

  1. 观察者模式的核心是定义一对多的依赖关系,主题状态变化时自动通知所有观察者,核心角色包括主题(被观察者)、观察者、具体主题、具体观察者;
  2. Spring 中通过ApplicationEvent(事件)和ApplicationListener(监听器)实现观察者模式,是业务开发中最常用的事件通知方式;
  3. 观察者模式适用于事件通知、状态监控、消息推送等场景,需注意控制观察者数量、处理异常传播、避免内存泄漏;
  4. 发布 - 订阅模式是观察者模式的分布式升级版,通过消息中间件实现主题与观察者的完全解耦,适用于跨服务的异步通知。
相关推荐
一只鹿鹿鹿2 小时前
研发中心数据安全管理规定(文件)
java·运维·开发语言·数据库·后端
小龙报2 小时前
【算法通关指南:算法基础篇】二分答案专题:1.木材加工 2.砍树
c语言·数据结构·c++·算法·启发式算法
旺旺仙贝呦2 小时前
Java常用注解
java·开发语言·python
南 阳2 小时前
Python从入门到精通day51
开发语言·python
智海观潮2 小时前
只用一周时间通过AI工具重写Next.js,Cloudflare推出vinext重建前端开发边界
开发语言·javascript·人工智能·大模型·web
忧郁缭绕2 小时前
Spring Boot Pf4j模块化能力设计思考
java·spring boot·后端
skywalk81632 小时前
OpenClaw启动后,web控制面板无法登录,返回信息:Not Found
开发语言·人工智能·openclaw
炸膛坦客2 小时前
单片机/C语言八股:(十一)指针的补充,包括指针的类型和大小
c语言·开发语言·单片机
天若有情6732 小时前
C++设计模式:tur函数——让对象自我裁决的条件选择器
java·c++·设计模式