观察者模式:从博客订阅到消息队列的解耦实践

news2025/7/10 23:55:29

观察者模式:从博客订阅到消息队列的解耦实践

一、模式核心:用事件驱动实现对象间松耦合

在新闻 APP 中,当热点事件发生时需要实时通知所有订阅用户;在电商系统中,库存变化需触发价格监控模块重新计算。这类场景的核心矛盾是:对象间存在依赖关系,但不能硬编码耦合。** 观察者模式(Observer Pattern)** 通过定义「发布 - 订阅」模型,让对象间的通知关系完全解耦,核心解决:

  • 数据变更通知:当主题(Subject)状态变化时,自动通知所有关联的观察者(Observer)
  • 模块解耦:观察者与主题无需互相知道具体实现,仅通过抽象接口交互

核心思想与 UML 类图

img

二、核心实现:构建通用事件通知框架

1. 定义主题接口(Subject)

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

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

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

    // 注销观察者
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    // 通知所有观察者
    protected void notifyObservers(Object data) {
        observers.forEach(observer -> observer.update(data));
    }

    // 抽象方法:主题状态变化时调用
    public abstract void updateState(Object newState);
}

2. 实现具体主题(ConcreteSubject)

public class StockSubject extends Subject {
    private double stockPrice; // 主题状态:股票价格

    public double getStockPrice() {
        return stockPrice;
    }

    @Override
    public void updateState(Object newState) {
        this.stockPrice = (double) newState;
        notifyObservers(stockPrice); // 状态变化时触发通知
    }
}

3. 定义观察者接口(Observer)

public interface Observer {
    // 接收主题通知的更新方法
    void update(Object data);
}

4. 实现具体观察者(ConcreteObserver)

public class InvestorObserver implements Observer {
    private String investorName;

    public InvestorObserver(String name) {
        this.investorName = name;
    }

    @Override
    public void update(Object data) {
        double price = (double) data;
        System.out.println("投资者 " + investorName + " 收到通知:股票价格更新为 " + price);
        // 执行具体业务逻辑(如触发交易策略)
    }
}

5. 客户端调用示例

public class ClientDemo {
    public static void main(String[] args) {
        // 创建主题与观察者
        StockSubject stock = new StockSubject();
        Observer investorA = new InvestorObserver("张三");
        Observer investorB = new InvestorObserver("李四");

        // 注册观察者
        stock.attach(investorA);
        stock.attach(investorB);

        // 主题状态变化,触发通知
        stock.updateState(15.5); // 输出:两位投资者收到价格更新通知
        stock.detach(investorB); // 注销观察者B
        stock.updateState(16.2); // 仅投资者A收到通知
    }
}

三、进阶:实现异步事件驱动与精准通知

1. 支持泛型的强类型通知

// 定义带泛型的观察者接口
public interface GenericObserver<T> {
    void update(T data);
}

// 主题支持特定类型的事件数据
public class GenericSubject<T> {
    private List<GenericObserver<T>> observers = new ArrayList<>();
    public void notifyObservers(T data) {
        observers.forEach(observer -> observer.update(data));
    }
}

// 使用示例:股票价格变化事件(Double类型)
GenericSubject<Double> stock = new GenericSubject<>();
stock.attach((Double price) -> System.out.println("精准通知:价格" + price));

2. 异步通知机制(解耦通知与业务处理)

public class AsyncSubject<T> extends GenericSubject<T> {
    private ExecutorService executor = Executors.newFixedThreadPool(5);

    @Override
    public void notifyObservers(T data) {
        // 使用线程池异步执行观察者逻辑
        executor.submit(() -> super.notifyObservers(data));
    }
}

// 优势:避免主题阻塞,提升系统吞吐量
// 注意:需处理异步带来的线程安全与数据一致性问题

3. 带事件类型的精准通知(基于 Guava EventBus)

// 定义具体事件类型
public class PriceUpdateEvent {
    private double newPrice;
    public PriceUpdateEvent(double price) {this.newPrice = price;}
    // getters...
}

// 使用Guava EventBus实现
EventBus eventBus = new EventBus();
eventBus.register(new Object() {
    @Subscribe
    public void onPriceUpdate(PriceUpdateEvent event) {
        // 仅接收PriceUpdateEvent类型的事件
        System.out.println("处理价格更新事件:" + event.getNewPrice());
    }
});
eventBus.post(new PriceUpdateEvent(18.7)); // 精准触发对应观察者

四、框架与源码中的观察者模式实践

1. Spring ApplicationEvent(同步通知)

  • 核心类ApplicationEvent(事件)、ApplicationListener(观察者)、ApplicationEventPublisher(主题)

  • 使用示例:

    // 定义自定义事件
    public class OrderPaidEvent extends ApplicationEvent {
        private String orderId;
        public OrderPaidEvent(Object source, String orderId) {
            super(source);
            this.orderId = orderId;
        }
    }
    
    // 注册观察者
    @Component
    public class OrderListener implements ApplicationListener<OrderPaidEvent> {
        @Override
        public void onApplicationEvent(OrderPaidEvent event) {
            // 处理订单支付后的业务(如更新库存、发送短信)
        }
    }
    
    // 发布事件
    applicationEventPublisher.publishEvent(new OrderPaidEvent(this, "1001"));
    

2. Android Listener 机制(异步回调)

  • 典型场景:按钮点击事件、网络请求回调

  • 实现原理:

    // 主题(Button)注册观察者(OnClickListener)
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 观察者逻辑
        }
    });
    

3. Kafka 消息队列(分布式观察者)

  • 主题:Kafka 的 Topic(如 “stock-price-topic”)
  • 观察者:Kafka 消费者组(Consumer Group)
  • 优势:支持百万级观察者的分布式通知,实现系统间的松耦合
// Kafka消费者示例(观察者)
KafkaConsumer<String, Double> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("stock-price-topic"));
while (true) {
    ConsumerRecords<String, Double> records = consumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, Double> record : records) {
        handlePriceUpdate(record.value()); // 处理通知
    }
}

五、避坑指南:正确使用观察者模式的 4 个要点

1. 避免内存泄漏(及时注销观察者)

  • ❌ 错误实践:观察者注册后未调用detach(),导致主题持有观察者强引用

  • ✅ 最佳实践:在观察者销毁时(如 Activity 销毁),主动从主题中注销

    @Override
    protected void onDestroy() {
        super.onDestroy();
        subject.detach(this); // 注销当前观察者
    }
    

2. 控制通知粒度(避免过度频繁通知)

  • 当主题状态变化频繁时(如实时数据流),增加防抖机制批量通知

    // 防抖示例:1秒内多次变化合并为一次通知
    public void updateState(Object newState) {
        if (lastUpdateTime + 1000 > System.currentTimeMillis()) {
            return; // 忽略短时间内的重复通知
        }
        lastUpdateTime = System.currentTimeMillis();
        super.updateState(newState);
    }
    

3. 处理循环依赖(观察者反向修改主题)

  • 当观察者更新时可能再次触发主题状态变化,需增加

    事件防火墙

    public void update(Object data) {
        if (isProcessingEvent) return; // 避免循环触发
        isProcessingEvent = true;
        // 业务逻辑
        isProcessingEvent = false;
    }
    

4. 反模式:滥用观察者导致复杂度上升

  • 当观察者链过深(如 A→B→C→D)时,改用责任链模式事件总线重构
  • 避免为简单的双向通信使用观察者(直接调用接口更高效)

六、总结:何时该用观察者模式?

适用场景核心特征典型案例
实时数据通知一个对象状态变化需触发多个对象的响应股票行情推送、邮件订阅系统
模块解耦需求对象间存在依赖但不想硬编码关联微服务事件驱动架构、GUI 事件处理
异步事件处理需要将通知与业务逻辑分离消息队列、日志异步写入

观察者模式通过「主题抽象 + 事件驱动」的设计,将对象间的依赖关系从「直接调用」转化为「事件订阅」,这是实现松耦合系统的核心模式之一。下一篇我们将深入探讨状态模式,解析有限状态机在电商订单系统中的设计与实现,敬请期待!

扩展思考:观察者模式 vs 发布 - 订阅模式

两者核心思想相似,但存在关键区别:

模式中介者通知方式应用场景
观察者模式主题直接关联观察者主题主动通知观察者本地模块间通信
发布 - 订阅模式事件总线作为中介发布者与订阅者无直接关联分布式系统、微服务间通信

理解这些差异,有助于在不同架构场景中选择最合适的设计方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2337458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ReportLab 导出 PDF(页面布局)

ReportLab 导出 PDF&#xff08;文档创建&#xff09; ReportLab 导出 PDF&#xff08;页面布局&#xff09; ReportLab 导出 PDF&#xff08;图文表格) PLATYPUS - 页面布局和排版 1. 设计目标2. 开始3. Flowables3.1. Flowable.draw()3.2. Flowable.drawOn(canvas,x,y)3.3. F…

【CVE-2024-10929】ARM CPU漏洞安全通告

安全之安全(security)博客目录导读 目录 一、概述 二、CVE详情 三、受影响产品 四、建议措施 五、致谢 六、版本历史 一、概述 在部分基于Arm架构的CPU中发现了一个潜在安全问题&#xff0c;称为Spectre-BSE&#xff08;Branch Status Eviction&#xff0c;分支状态驱逐…

OpenCV 图形API(33)图像滤波-----高斯模糊函数gaussianBlur()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用高斯滤波器对图像进行模糊处理。 该函数使用指定的高斯核对源图像进行滤波。输出图像必须与输入图像具有相同的类型和通道数。 cv::gapi::g…

【2025最新版】火鸟门户v8.5系统源码+PC、H5、小程序 +数据化大屏插件

一.介绍 火鸟地方门户系统V8.5源码 系统包含4端&#xff1a; PCH5小程序APP 二.搭建环境 系统环境&#xff1a;CentOS、 运行环境&#xff1a;宝塔 Linux 网站环境&#xff1a;Nginx 1.2.22 MySQL 5.6 PHP-7.4 常见插件&#xff1a;fileinfo &#xff1b; redis 三.测…

关于 传感器 的详细解析,涵盖定义、分类、工作原理、常见类型、应用领域、技术挑战及未来趋势,结合实例帮助理解其核心概念

以下是关于 传感器 的详细解析&#xff0c;涵盖定义、分类、工作原理、常见类型、应用领域、技术挑战及未来趋势&#xff0c;结合实例帮助理解其核心概念&#xff1a; 一、传感器的定义与核心功能 1. 定义 传感器&#xff08;Sensor&#xff09;是一种能够将物理量&#xff…

EtherCAT转ProfiNet边缘计算网关配置优化:汽车制造场景下PLC与机器人协同作业案例

1.行业背景与需求分析 智能汽车焊装车间是汽车制造的核心工艺环节&#xff0c;某德国豪华品牌在其上海MEB工厂新建的焊装车间中&#xff0c;采用西门子S7-1500PLC作为ProfiNet主站&#xff0c;负责整线协调与质量追溯&#xff1b;同时部署KUKAKR1500Titan机器人&#xff08;Eth…

HTTP协议 --- 超文本传输协议 和 TCP --- 传输控制协议

是基于 TCP 协议的 80 端口的一种 C/S 架构协议。 特点&#xff1a;无状态 --- 数据传输完成后&#xff0c;会断开 TCP 连接&#xff0c;哪怕浏览器还正常运行。 请求报文 --- 方法 响应报文 --- 状态码 是一种面向连接的可靠传输协议 。 面向连接 --- 在传输数据之前&am…

类和对象(下篇)(详解)

【本节目标】 1. 再谈构造函数 2. Static成员 3. 友元 4. 内部类 5. 再次理解封装 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 #include <iostream> using name…

LeetCode【剑指offer】系列(位运算篇)

剑指offer15.二进制中1的个数 题目链接 题目&#xff1a;编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 思路一&#xff…

网络安全领域的AI战略准备:从概念到实践

网络安全领域的AI准备不仅涉及最新工具和技术的应用&#xff0c;更是一项战略必需。许多企业若因目标不明确、数据准备不足或与业务重点脱节而未能有效利用AI技术&#xff0c;可能面临严重后果&#xff0c;包括高级网络威胁数量的激增。 AI准备的核心要素 构建稳健的网络安全…

MacOs下解决远程终端内容复制并到本地粘贴板

常常需要在服务器上捣鼓东西&#xff0c;同时需要将内容复制到本地的需求。 1-内容是在远程终端用vim打开&#xff0c;如何用vim的类似指令达到快速复制到本地呢&#xff1f; 假设待复制的内容&#xff1a; #include <iostream> #include <cstring> using names…

基于PAI+专属网关+私网连接:构建全链路 Deepseek 云上私有化部署与模型调用架构

DeepSeek - R1 是由深度求索公司推出的首款推理模型&#xff0c;该模型在数学、代码和推理任务上的表现优异&#xff0c;市场反馈火爆。在大模型技术商业化进程中&#xff0c;企业级用户普遍面临四大核心挑战&#xff1a; 算力投入成本高昂&#xff1a;构建千亿参数级模型的训…

【cocos creator 3.x】cocos creator2.x项目升级3.x项目改动点

1、基本改动 基本改动&#xff1a;去掉了cc.&#xff0c;改成在顶部添加导入 项目升级时候直接将cc.去掉&#xff0c;根据提示添加引用 node只保留position,scale,rotation,layer 其余属性如opacity&#xff0c;如果需要使用需要在节点手动添加UIOpacity组件 3d层和ui层分开…

List基础与难度题

1. 向 ArrayList 中添加元素并打印 功能描述&#xff1a; 程序创建一个空的 ArrayList 集合&#xff0c;用于存储字符串类型的元素。向该 ArrayList 中依次添加指定的字符串元素。使用增强型 for 循环遍历 ArrayList 中的所有元素&#xff0c;并将每个元素打印输出到控制台。 …

Oracle19C低版本一天遭遇两BUG(ORA-04031/ORA-600)

昨天帮朋友看一个系统异常卡顿的案例&#xff0c;在这里分享给大家 环境&#xff1a;Exadata X8M 数据库版本19.11 1.系统报错信息 表象为系统卡顿&#xff0c;页面无法刷出&#xff0c;登陆到主机上看到节点1 系统等待存在大量的 cursor: pin S wait on X等待 查看两个节…

【4.1.-4.20学习周报】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract一、方法介绍1.1HippoRAG 1.2HippoRAG2二、实验2.1实验概况2.2实验代码2.3实验结果 总结 摘要 本博客介绍了论文《From RAG to Memory: Non-Parametri…

python学习—合并多个word文档

系列文章目录 python学习—合并TXT文本文件 python学习—统计嵌套文件夹内的文件数量并建立索引表格 python学习—查找指定目录下的指定类型文件 python学习—年会不能停&#xff0c;游戏抽签抽奖 python学习—循环语句-控制流 python学习—合并多个Excel工作簿表格文件 pytho…

[Python] UV工具入门使用指南——小试牛刀

背景 MCP开发使用到了uv&#xff0c;简单记录一下&#xff1a; 为什么MCP更推荐使用uv进行环境管理&#xff1f; MCP 依赖的 Python 环境可能包含多个模块&#xff0c;uv 通过 pyproject.toml 提供更高效的管理方式&#xff0c;并且可以避免 pip 的一些依赖冲突问题。…

PclSharp ——pcl的c#nuget包

简介&#xff1a; NuGet Gallery | PclSharp 1.8.1.20180820-beta07 下载.NET Framework 4.5.2 Developer Pack&#xff1a; 下载 .NET Framework 4.5.2 Developer Pack Offline Installer 离线安装nupkg&#xff1a; nupkg是visual studio 的NuGet Package的一个包文件 安…

MGR实现mysql高可用性

一。MGR和PXC的区别 1. PXC的消息广播机制是在节点间循环的&#xff0c;需要所有节点都确认消息&#xff0c;因此只要有一个节点故障&#xff0c;则会导致整个PXC都发生故障。而MGR则是多数派投票模式&#xff0c;个别少数派节点故障时&#xff0c;一般不影响整体的可用性。这…