【西瓜带你学设计模式 | 第二期-观察者模式】观察者模式——推模型与拉模型实现、优缺点与适用场景

文章目录

    • 前言
      1. 观察者模式是什么?
      1. 为什么要用观察者?能解决什么问题?
      1. 观察者模式的实现思路(Java 视角)
      1. 推模型(Push)+ 同步通知
      • 4.1 角色定义:Observer 接口
      • 4.2 角色定义:Subject 接口
      • 4.3 具体主题:维护观察者列表并通知
      • 4.4 两个具体观察者
      • 4.5 测试运行
      1. 拉模型(Pull)实现思路
      1. 常见的优缺点
      • 6.1 优点
      • 6.2 缺点
      1. 常见实现方式(工程上怎么选)
      • 7.1 同步方式(上面的示例)
      • 7.2 异步方式(事件队列/线程池)
      1. 观察者模式的适用场景
      1. 注意事项(Java 工程常见坑)
      1. 一句话总结:怎么选实现方式?

前言

在软件开发中,"一个对象状态变化后,需要通知多个对象做相应处理"非常常见。比如:支付成功后要发短信、更新订单状态、写日志、触发埋点......如果让发布者直接依赖每个处理方,就会导致耦合度高、扩展困难
观察者模式 就是为了解决"发布-订阅的通知关系松耦合"问题:谁关心通知就订阅,发布者无需知道订阅者的具体实现。


1. 观察者模式是什么?

观察者模式(Observer Pattern):当一个对象(主题 Subject)的状态发生改变时,所有依赖它的对象(观察者 Observer)都会收到通知并自动更新。

常见用途:

  • 事件驱动系统(UI 按钮点击、消息推送)
  • 消息通知/监听机制(订单事件、告警事件)
  • 框架内部组件解耦(Spring 事件机制属于这一思想)

2. 为什么要用观察者?能解决什么问题?

观察者模式通常用于以下场景:

  1. 一对多依赖:一个事件对应多个处理逻辑。
  2. 解耦发布者与订阅者:发布者不需要知道订阅者是谁、做什么。
  3. 动态可扩展:运行时可以增加/移除观察者,无需修改发布者代码。
  4. 统一事件通知机制:让业务逻辑围绕"事件"组织,而非硬编码调用链。

3. 观察者模式的实现思路(Java 视角)

GoF 观察者模式常见角色:

  1. Observer(观察者) :定义接收通知的方法,如 update(...)
  2. ConcreteObserver(具体观察者) :实现 update(...),处理通知内容
  3. Subject(主题/被观察者) :维护观察者集合,如 attach/detach,并提供 notify(...)
  4. ConcreteSubject(具体主题) :状态变化时调用 notify(...) 通知所有观察者

此外实现方式常见两类:

  • 推模型(Push) :通知时直接把数据推给观察者 (如 update(message)
  • 拉模型(Pull) :通知时只提醒观察者"有变化",数据由观察者自己再去主题读取

下面给出一个推模型同步通知示例。


4. 推模型(Push)+ 同步通知

4.1 角色定义:Observer 接口

java 复制代码
public interface Observer {
    void update(String message);
}

4.2 角色定义:Subject 接口

java 复制代码
public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers(String message);
}

4.3 具体主题:维护观察者列表并通知

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

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

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

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

    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }

    // 状态变化触发通知
    public void publish(String message) {
        System.out.println("发布新闻: " + message);
        notifyObservers(message);
    }
}

4.4 两个具体观察者

java 复制代码
public class EmailSubscriber implements Observer {
    @Override
    public void update(String message) {
        System.out.println("邮件订阅收到: " + message);
    }
}
java 复制代码
public class WechatSubscriber implements Observer {
    @Override
    public void update(String message) {
        System.out.println("微信订阅收到: " + message);
    }
}

4.5 测试运行

java 复制代码
public class ObserverDemo {
    public static void main(String[] args) {
        NewsPublisher publisher = new NewsPublisher();

        Observer email = new EmailSubscriber();
        Observer wechat = new WechatSubscriber();

        publisher.attach(email);
        publisher.attach(wechat);

        publisher.publish("观察者模式上线啦!");
    }
}

输出示例:

复制代码
发布新闻: 观察者模式上线啦!
邮件订阅收到: 观察者模式上线啦!
微信订阅收到: 观察者模式上线啦!

5. 拉模型(Pull)实现思路

拉模型的关键点:notify 时不把具体数据传给观察者,而是让观察者通过主题"再取最新状态"。

典型变化:

  • update() 可能不带参数void update()
  • 观察者在 update() 内调用 subject.getState() 拉取数据

拉模型常见于:

  • 数据更新频繁,通知只需"提醒"
  • 希望降低通知参数复杂度

6. 常见的优缺点

6.1 优点

  • 松耦合:发布者不依赖具体观察者实现
  • 易扩展:新增观察者通常不改发布者逻辑
  • 符合开放封闭原则:对扩展开放,对修改关闭
  • 事件驱动:让系统结构更符合业务事件流

6.2 缺点

  • 通知链不可控:观察者多时通知成本高
  • 异常传播问题:某个观察者失败可能影响后续通知(需隔离 try-catch)
  • 线程/时序复杂:多线程下 attach/detach/notify 需要线程安全策略
  • 可能形成"隐式依赖":调试时不易追踪谁被通知了

7. 常见实现方式(工程上怎么选)

7.1 同步方式(上面的示例)

  • publish() 会等待所有观察者 update() 完成
  • 简单直观,适合观察者处理很快的场景

7.2 异步方式(事件队列/线程池)

  • 观察者通知由线程池执行,发布者不阻塞
  • 常见于:消息通知、埋点上报、告警推送等

工程实践通常会结合:线程池 + 超时 + 重试/熔断 + 观测(日志/指标)。


8. 观察者模式的适用场景

适合:

  • 一对多事件通知(同一事件多个处理方)
  • 事件处理可扩展(订阅者经常增减)
  • 希望降低发布者与处理方耦合度
  • UI/业务事件驱动系统

不适合:

  • 你需要强一致的"调用返回结果"链(观察者是通知,不是请求-响应)
  • 订阅者数量极少且固定,直接调用反而更简单
  • 观察者处理强依赖顺序且必须同步完成(可能需要更复杂的编排机制)

9. 注意事项(Java 工程常见坑)

  1. 线程安全
    • 如果运行中频繁 attach/detach,且 notify 可能并发:
      • 可考虑 CopyOnWriteArrayList 或加锁策略
  2. 异常隔离
    • notifyObservers 中建议 try-catch,避免一个观察者异常导致后面都不执行
  3. 避免通知风暴
    • 观察者过多/处理过慢时需要异步化或做合并/节流

10. 一句话总结:怎么选实现方式?

  • 观察者模式(Observer) 适合"状态变化需要通知多个处理方,且希望发布者与订阅者松耦合"的场景;
  • 通知数据传给观察者用推模型 ,只提醒观察者再去拉数据用拉模型
  • 工程上观察者多且慢时优先考虑异步通知
相关推荐
倾颜2 小时前
我是怎么把单 Tool Calling 升级成多 Tool Runtime 的
前端·后端·langchain
Counter-Strike大牛2 小时前
SpringBoot项目调用数据库函数报错Result consisted of more than one row
数据库·spring boot·后端
清汤饺子2 小时前
Superpowers:给 AI 编程 Agent 装上"工程化超能力"
前端·javascript·后端
念何架构之路2 小时前
Go语言表达式的求值顺序
开发语言·后端·golang
zihao_tom2 小时前
Springboot-配置文件中敏感信息的加密:三种加密保护方法比较
java·spring boot·后端
程序员buddha2 小时前
Java面试八股文框架篇
java·开发语言·面试
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于Java的医药进出口交易系统设计与实现为例,包含答辩的问题和答案
java·开发语言
Touch&2 小时前
Windows11多个JDK版本(Java8、Java11、Java17、Java21)下载安装和切换
java·jdk·jdk多个版本切换
.生产的驴2 小时前
1Panel实战|SpringColud微服务部署生产环境一键部署Docker+Nacos+MySQL 数据定时备份 控制台 安全高效易维护
服务器·后端·mysql·spring cloud·docker·微服务·信息可视化