前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、观察者模式(Observer Pattern)

1. 模式概述

观察者模式 是一种 行为型设计模式,它定义了对象之间 一对多的依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会 自动收到通知并更新

核心思想“发布-订阅”机制 —— 被观察者不关心谁在听,只负责广播;观察者不主动拉取,只被动响应。

观察者模式 = 状态驱动 + 自动通知

  • 它不是“轮询检查”,而是“事件驱动”——让系统具备 响应式能力

类比理解:

  • 微信公众号:你关注(订阅)某个号,它一发新文章,你就收到推送;
  • 天气预报系统:气象站数据更新 → 自动通知所有显示屏、手机 App、网站;
  • 股票价格变动:股价变化 → 所有盯盘软件实时刷新。

2. 使用场景

观察者模式适用于以下情况:

  • 一个对象的 状态变化需要通知多个其他对象
  • 对象之间存在 松耦合的“发布-订阅”关系
  • 需要 动态增删监听者(如插件系统、事件总线);
  • 希望 避免硬编码调用链(如 A 改变 → B/C/D 自动响应)。

典型应用

  • GUI 事件处理(按钮点击 → 多个监听器响应);
  • Spring 的 ApplicationEventPublisher
  • 消息队列(Producer → Broker → Multiple Consumers);
  • 游戏中的成就系统(击杀敌人 → 触发成就 + 计分 + 音效)。

3. 示例程序:用户登录事件通知(优化版)

1)观察者接口 UserObserver

/**
 * 观察者接口(Observer)
 */
public interface UserObserver {
    void update(User user);
}

2)被观察者类 User

import java.util.ArrayList;
import java.util.List;

/**
 * 被观察者(Subject)
 * 管理观察者列表,并在状态变化时通知
 */
public class User {
    private String name;
    private String password;

    // 观察者列表(支持多个)
    private final List<UserObserver> observers = new ArrayList<>();

    // 注册观察者
    public void addObserver(UserObserver observer) {
        observers.add(observer);
    }

    // 移除观察者
    public void removeObserver(UserObserver observer) {
        observers.remove(observer);
    }

    // 通知所有观察者
    private void notifyObservers() {
        for (UserObserver observer : observers) {
            observer.update(this);
        }
    }

    // 登录操作(状态变更点)
    public void login(String name, String password) {
        this.name = name;
        this.password = password;
        System.out.println("正在登陆的用户为:" + name + ",密码为:" + password);

        // 状态已更新,通知所有观察者
        notifyObservers();
    }

    // Getter(供观察者读取状态)
    public String getName() { return name; }
    public String getPassword() { return password; }
}

3)具体观察者

a、日志观察者

public class LoggingObserver implements UserObserver {
    @Override
    public void update(User user) {
        System.out.println("[日志] 监听到用户登录: " + user.getName());
    }
}

b、安全审计观察者

public class SecurityAuditObserver implements UserObserver {
    @Override
    public void update(User user) {
        System.out.println("[安全] 记录登录事件: " + user.getName() + " at " + System.currentTimeMillis());
    }
}

4)客户端测试 Main

  • 支持 任意数量观察者
  • 观察者 无需知道彼此存在
  • 新增观察者(如 EmailObserver无需修改 User
/**
 * 客户端(Client)
 */
public class Main {
    public static void main(String[] args) {
        User user = new User();

        // 注册多个观察者
        user.addObserver(new LoggingObserver());
        user.addObserver(new SecurityAuditObserver());

        // 触发登录(状态变更)
        user.login("healerjean", "password");
    }
}

输出:

正在登陆的用户为:healerjean,密码为:password
[日志] 监听到用户登录: healerjean
[安全] 记录登录事件: healerjean at 1700000000000

5)模式角色

  • 被观察者 持有观察者集合(非单个);
  • 通知时 遍历所有观察者,调用其 update() 方法。
角色 职责 示例
Subject(被观察者/目标) 管理观察者列表,提供注册/注销接口,状态变更时通知所有观察者 User / Observable
Observer(观察者接口) 定义更新接口,供被观察者回调 UserObserver
ConcreteSubject(具体被观察者) 存储状态,状态变化时触发通知 User
ConcreteObserver(具体观察者) 实现更新逻辑,响应状态变化 LoggingObserver, EmailObserver

4. FQA

1)模式优点

  • 松耦合:被观察者与观察者之间无直接依赖;
  • 支持广播通信:一对多通知天然支持;
  • 动态订阅:运行时可增删观察者;
  • 符合开闭原则:新增观察者无需修改被观察者。

2)注意事项

  • 内存泄漏风险:若未及时 removeObserver,观察者可能无法被GC
  • 通知顺序不确定:观察者执行顺序不可控(需额外排序);
  • 性能问题:大量观察者或高频通知可能导致卡顿;
  • 异常传播:一个观察者抛异常可能中断后续通知(建议 try-catch 隔离)。

3)观察者模式 vs 访问者模式

对比维度 观察者模式(Observer) 访问者模式(Visitor)
目的 状态变化时自动通知依赖对象 将操作从数据结构中分离,集中处理
触发方式 被观察者主动通知(事件驱动) 客户端主动调用访问(请求驱动)
核心关系 一对多(1 subject → N observers) 一对一访问,遍历多个元素
谁持有谁引用 被观察者持有观察者列表 元素持有访问者接口;访问者知道所有元素类型
扩展新行为 ✅ 极易(加一个 Observer) ✅ 极易(加一个 Visitor)
扩展新元素 ✅ 容易 ❌ 困难(需改所有 Visitor)
典型场景 事件系统、消息通知、GUI 监听 编译器 AST 遍历、报表导出、结构计算
关键词 监听、通知、订阅 访问、遍历、处理