当前位置: 首页 > article >正文

设计模式 --- 观察者模式

设计模式 --- 观察者模式

  • 什么是观察者模式
  • 观察者模式典型应用 --- C#中的事件
  • 使用观察者模式实现事件处理机制

什么是观察者模式

  • 观察者模式(Observer Pattern)是一种行为型设计模式,用于在对象之间建立一对多的依赖关系。当一个对象(称为“主题”Subject)的状态发生改变时,所有依赖它的对象(称为“观察者”Observer)会自动收到通知并更新。这种模式的核心是解耦主题和观察者,让它们可以独立变化。
  • 核心思想
    主题(Subject):维护一个观察者列表,提供注册、注销和通知机制。
    观察者(Observer):定义统一的更新接口,当主题状态变化时被调用。
    松耦合:主题不需要知道观察者的具体实现,只需通过接口通知

观察者模式典型应用 — C#中的事件

事件是什么?

  • 事件(Event)是 C# 中的一种语言机制,允许一个对象(发布者)在特定动作发生时,通知其他对象(订阅者)执行响应逻辑。
    它本质上是基于委托(Delegate)的封装,提供了一种安全、可控的观察者模型
  • 核心特点
  • 主题定义事件:声明事件并决定何时触发。
  • 观察者注册方法:将方法绑定到事件,事件触发时自动调用。
  • 解耦设计:主题和观察者无需直接依赖彼此的具体实现。

事件的原理

  • 底层机制:委托 + 观察者模式
  • 委托(Delegate)
    事件本质是一个多播委托MulticastDelegate),可以存储多个方法引用。当事件触发时,所有绑定的方法会被依次调用。

  • 观察者模式(Observer Pattern)

    • 发布者(Subject):维护一个订阅者列表(通过委托链实现),提供订阅(+=)和取消订阅(-=)接口。
    • 订阅者(Observer):向发布者注册事件处理方法,事件触发时自动执行。

完整示例:温度监控系统

  • 场景描述
  • 发布者TemperatureMonitor 类,监控温度变化。
  • 订阅者
    • AlertSystem:温度过高时触发警报。
    • Logger:记录温度事件到日志。
  • 关键流程:
  • 定义事件:使用 event 关键字声明一个委托类型的事件。
  • 订阅事件:通过 += 将方法添加到事件的委托链。
  • 触发事件:在特定条件下调用事件,执行所有订阅的方法。

代码实现

using System;// 1. 定义事件参数(传递温度数据)
public class TemperatureEventArgs : EventArgs
{public double Temperature { get; }public TemperatureEventArgs(double temperature){Temperature = temperature;}
}// 2. 发布者(Subject)
public class TemperatureMonitor
{// 声明事件(基于泛型 EventHandler<T> 委托)// EventHandler<T> 是 .NET 内置的泛型委托,定义形式为:  // delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)public event EventHandler<TemperatureEventArgs> TemperatureExceeded;private double _currentTemperature;public double Threshold { get; set; } = 30.0;public void UpdateTemperature(double newTemperature){_currentTemperature = newTemperature;Console.WriteLine($"当前温度: {_currentTemperature}°C");// 触发条件:温度超过阈值if (_currentTemperature > Threshold){OnTemperatureExceeded(new TemperatureEventArgs(_currentTemperature));}}// 触发事件的方法(protected virtual 便于派生类扩展)protected virtual void OnTemperatureExceeded(TemperatureEventArgs e){// 线程安全:临时保存事件引用,避免多线程下订阅者被移除导致的空引用var handler = TemperatureExceeded;handler?.Invoke(this, e); // 等同于 if (handler != null) handler(this, e);}
}// 3. 订阅者(Observer)
public class AlertSystem
{public void HandleHighTemperature(object sender, TemperatureEventArgs e){Console.WriteLine($"警报!温度过高: {e.Temperature}°C");}
}public class Logger
{public void LogTemperatureEvent(object sender, TemperatureEventArgs e){Console.WriteLine($"[日志] 温度事件: {e.Temperature}°C");}
}// 4. 使用示例
class Program
{static void Main(){var monitor = new TemperatureMonitor();var alert = new AlertSystem();var logger = new Logger();// 订阅事件monitor.TemperatureExceeded += alert.HandleHighTemperature;monitor.TemperatureExceeded += logger.LogTemperatureEvent;// 模拟温度变化monitor.UpdateTemperature(25); // 不触发monitor.UpdateTemperature(32); // 触发事件monitor.UpdateTemperature(28); // 不触发monitor.UpdateTemperature(35); // 再次触发// 取消订阅monitor.TemperatureExceeded -= logger.LogTemperatureEvent;monitor.UpdateTemperature(40); // 仅触发警报}
}

输出结果

当前温度: 25°C
当前温度: 32°C
警报!温度过高: 32°C
[日志] 温度事件: 32°C
当前温度: 28°C
当前温度: 35°C
警报!温度过高: 35°C
[日志] 温度事件: 35°C
当前温度: 40°C
警报!温度过高: 40°C
  • 观察者模式
  • TemperatureMonitor 是 Subject,维护订阅者列表(通过 TemperatureExceeded 事件的委托链)。
  • AlertSystemLogger` 是 Observers,通过订阅事件实现解耦。

总结

  • C# 中的事件是基于委托的观察者模式实现,通过 event 关键字提供安全的订阅机制。它广泛用于:
  • GUI 编程(如按钮点击)
  • 异步通知等场景,是解耦代码的关键工具

使用观察者模式实现事件处理机制

以下是一个使用 Java 手动实现事件处理机制的完整示例,基于观察者模式自定义接口,支持线程安全的订阅、取消订阅和事件触发功能。


事件对象(Event

public class Event {private String message;private long timestamp;public Event(String message) {this.message = message;this.timestamp = System.currentTimeMillis();}public String getMessage() {return message;}public long getTimestamp() {return timestamp;}
}

事件监听器接口(EventListener

@FunctionalInterface
public interface EventListener {void onEvent(Event event);
}

事件发布者(EventPublisher

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;public class EventPublisher {// 使用线程安全的集合存储监听器private final List<EventListener> listeners = new CopyOnWriteArrayList<>();// 订阅事件public void subscribe(EventListener listener) {listeners.add(listener);System.out.println("订阅成功: " + listener);}// 取消订阅public void unsubscribe(EventListener listener) {listeners.remove(listener);System.out.println("取消订阅: " + listener);}// 触发事件public void publishEvent(String message) {Event event = new Event(message);System.out.println("触发事件: " + event.getMessage());for (EventListener listener : listeners) {try {listener.onEvent(event);} catch (Exception e) {System.err.println("监听器处理异常: " + e.getMessage());}}}
}

定义具体监听器

public class Main {public static void main(String[] args) {EventPublisher publisher = new EventPublisher();// 监听器1:Lambda表达式实现EventListener listener1 = event -> System.out.printf("[Listener1] 收到事件: %s (时间: %d)%n",event.getMessage(), event.getTimestamp());// 监听器2:匿名类实现EventListener listener2 = new EventListener() {@Overridepublic void onEvent(Event event) {System.out.printf("[Listener2] 处理事件: %s%n", event.getMessage());}};// 订阅publisher.subscribe(listener1);publisher.subscribe(listener2);// 触发事件publisher.publishEvent("Hello World!");// 取消订阅listener2后再次触发publisher.unsubscribe(listener2);publisher.publishEvent("Another Message");}
}

输出结果

订阅成功: Main$$Lambda$1/0x0000000800b8a440@6d311334
订阅成功: Main$1@75bd9247
触发事件: Hello World!
[Listener1] 收到事件: Hello World! (时间: 1717500000000)
[Listener2] 处理事件: Hello World!
取消订阅: Main$1@75bd9247
触发事件: Another Message
[Listener1] 收到事件: Another Message (时间: 1717500001000)

关键设计解析

  • 3.1 线程安全
  • CopyOnWriteArrayList
    使用线程安全的集合存储监听器,确保在遍历过程中(如触发事件时)修改监听器列表不会导致 ConcurrentModificationException
  • 无锁设计
    添加/删除监听器时自动处理并发,无需显式加锁。
  • 观察者模式
  • Subject(主题)EventPublisher 维护监听器列表并提供订阅/取消订阅接口。
  • Observer(观察者)EventListener 接口的实现类,定义事件响应逻辑。

如果需要,可改为异步事件处理

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class AsyncEventPublisher extends EventPublisher {private final ExecutorService executor = Executors.newCachedThreadPool();@Overridepublic void publishEvent(String message) {Event event = new Event(message);System.out.println("触发事件: " + event.getMessage());for (EventListener listener : listeners) {executor.submit(() -> {try {listener.onEvent(event);} catch (Exception e) {System.err.println("监听器处理异常: " + e.getMessage());}});}}
}

相关文章:

设计模式 --- 观察者模式

设计模式 --- 观察者模式 什么是观察者模式观察者模式典型应用 --- C#中的事件使用观察者模式实现事件处理机制 什么是观察者模式 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;用于在对象之间建立一对多的依赖关系。当一个对象&#x…...

电商核心指标解析与行业趋势:数据驱动的增长策略【大模型总结】

电商核心指标解析与行业趋势&#xff1a;数据驱动的增长策略 在电商领域&#xff0c;数据是决策的核心。从流量监测到用户行为分析&#xff0c;从价格策略到技术赋能&#xff0c;每一个环节的优化都离不开对核心指标的深度理解。本文结合行业最新趋势与头部平台实践&#xff0…...

I/O进程4

day4 九、信号灯集 1.概念 信号灯(semaphore)&#xff0c;也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制&#xff1b;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。 通过信号灯集实现共享内存的同步操作。 2.编程…...

【语法】C++的list

目录 为什么会有list&#xff1f; 迭代器失效&#xff1a; list和vector的迭代器不同的地方&#xff1a; list的大部分用法和vector都很像&#xff0c;例如push_back&#xff0c;构造&#xff0c;析构&#xff0c;赋值重载这些就不再废话了&#xff0c;本篇主要讲的是和vecto…...

【算法笔记】并查集详解

&#x1f680; 并查集&#xff08;Union-Find&#xff09;详解&#xff1a;原理、实现与优化 并查集&#xff08;Union-Find&#xff09;是一种非常高效的数据结构&#xff0c;用于处理动态连通性问题&#xff0c;即判断若干个元素是否属于同一个集合&#xff0c;并支持集合合…...

【Ai/Agent】Windows11中安装CrewAI过程中的错误解决记录

CrewAi是什么&#xff0c;可以看之下之前写的 《初识CrewAI多智能代理团队协框架》 (注&#xff1a;这篇是基于linux系统下安装实践的) 基于以下记录解决问题后&#xff0c;可以再回到之前的文章继续进行CrewAI的安装 遇到问题 在windows系统中安装 CrewAi 不管是使用 pip 或者…...

OSPF的数据报文格式【复习篇】

OSPF协议是跨层封装的协议&#xff08;跨四层封装&#xff09;&#xff0c;直接将应用层的数据封装在网络层协议之后&#xff0c;IP协议包中协议号字段对应的数值为89 OSPF的头部信息&#xff1a; 所有的数据共有的信息字段 字段名描述版本当前OSPF进程使用的版本&#xff08;…...

[leetcode]查询区间内的所有素数

一.暴力求解 #include<iostream> #include<vector> using namespace std; vector<int> result; bool isPrime(int i) { if (i < 2) return false; for (int j 2;j * j < i;j) { if (i % j 0) { …...

【力扣刷题实战】Z字形变换

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 力扣题目&#xff1a;Z字形变换 题目描述 解题思路 问题理解 算法选择 具体思路 解题要点 完整代码&#xff08;C&#xff09; 兄弟们共勉 &#xff01;&#xff01;&#xff01; 每篇前言 博客主页&#xff1a;小卡…...

【RK3588 嵌入式图形编程】-SDL2-扫雷游戏-添加地雷到网格

添加地雷到网格 文章目录 添加地雷到网格1、概述2、更新Globals.h3、在随机单元格中放置地雷4、更新单元格以接收地雷5、渲染地雷图像6、开发助手7、完整代码8、总结在本文中,我们将更新游戏以在网格中随机放置地雷,并在单元格被清除时渲染它们。 1、概述 在我们扫雷游戏教程…...

Fortran 中读取 MATLAB 生成的数据文件

在 Fortran 中读取 MATLAB 生成的数据文件&#xff0c;可以通过以下几种方法实现&#xff0c;包括使用开源工具和手动解析&#xff1a; 1. 使用开源工具&#xff1a;MATFOR MATFOR 是一个商业/开源混合工具&#xff08;部分功能免费&#xff09;&#xff0c;提供 Fortran 与 M…...

Kubernetes 入门篇之网络插件 calico 部署与安装

在运行kubeadm init 和 join 命令部署好master和node节点后&#xff0c;kubectl get nodes 看到节点都是NotReady状态&#xff0c;这是因为没有安装CNI网络插件。 kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Not…...

力扣题解:142. 环形链表 II

在链表学习中&#xff0c;我们已经了解了单链表和双链表&#xff0c;两者的最后一个结点都会指向NULL&#xff1b;今天我们介绍的循环列表则不同&#xff0c;其末尾结点指向的这是链表中的一个结点。 循环链表是一种特殊类型的链表&#xff0c;其尾节点的指针指向头节点&#…...

latex模板文件

LaTeX 是一款广泛应用于学术领域的​​文档排版系统​​&#xff0c;尤其以其在数学公式、科学符号和复杂技术文档排版中的强大能力著称。虽然它本身并非专门的“数学软件”&#xff0c;但在处理数学相关内容时表现尤为出色。 1. LaTeX 的核心特点​ 数学公式支持​​&#xff…...

BLE 协议栈事件驱动机制详解

在 BlueNRG-LP 等 BLE 系统中,事件驱动是控制状态转移、数据交互和外设协作的基础。本文将深入讲解 BLE 协议栈中事件的来源、分发流程、处理结构与实际工程实践策略,帮助你构建稳定、可维护的 BLE 系统。 📦 一、BLE 事件的来源分类 BLE 协议栈中的事件严格来自协议栈本身…...

Rust 之四 运算符、标量、元组、数组、字符串、结构体、枚举

概述 Rust 的基本语法对于从事底层 C/C 开发的人来说多少有些难以理解&#xff0c;虽然官方有详细的文档来介绍&#xff0c;不过内容是相当的多&#xff0c;看起来也费劲。本文通过将每个知识点简化为 一个 DEMO 每种特性各用一句话描述的形式来简化学习过程&#xff0c;提高学…...

fuse-python使用fuse来挂载fs

winfsp 安装winfsp,https://winfsp.dev/ fusepy python安装fusepy #!/usr/bin/env python3 import os import stat from fuse import FUSE, FuseOSError, Operationsclass Passthrough(Operations):def __init__(self, root):self.root root# 辅助函数&#xff1a;将挂载点…...

基于ueditor编辑器的功能开发之增加自定义一键排版功能

用户有自己的文章格式&#xff0c;要求复制或者粘贴进来的文章能够一键排版&#xff0c;不需要手动调试 这个需求的话咱们就需要自己去注册一个事件啦&#xff0c;这里我没有修改源码&#xff0c;而是在编辑器初始化之后给他注册了一个事件 我的工具列表变量 vue组件中data中…...

内核态切换到用户态

内核态切换到用户态 是操作系统中 CPU 执行模式的一种切换过程&#xff0c;涉及从高权限的内核态&#xff08;Kernel Mode&#xff09;切换到低权限的用户态&#xff08;User Mode&#xff09;。以下是详细解释&#xff1a; 1. 什么是内核态和用户态&#xff1f; 内核态&#…...

win10离线环境下配置wsl2和vscode远程开发环境

win10离线环境下配置wsl2和vscode远程开发环境 环境文件准备wsl文件准备vscode文件准备 内网环境部署wsl环境部署vscode环境部署 迁移后Ubuntu中的程序无法启动 环境 内网机&#xff1a;win10、wsl1 文件准备 wsl文件准备 在外网机上的wsl安装Ubuntu24.04&#xff0c;直接在…...

AWS弹性容器服务(AWS Elastic Container Service,ECS)概述

李升伟 编译 标签&#xff1a;AWS | ECS | 容器 | Docker AWS弹性容器服务&#xff08;AWS Elastic Container Service&#xff0c;ECS&#xff09;简介 AWS弹性容器服务&#xff08;ECS&#xff09;是一项完全托管的容器编排服务&#xff0c;支持运行、管理和扩展容器化应用…...

Redis过期key处理、内存淘汰策略与缓存一致性策略实践方案

在现代的高性能应用开发中&#xff0c;Redis作为一款极为热门的内存数据库&#xff0c;其快速的读写性能和丰富的数据结构使其在缓存、消息队列等诸多领域得到了广泛应用。然而&#xff0c;在实际使用过程中&#xff0c;处理好Redis过期key、选择合适的内存淘汰策略以及确保缓存…...

@linux系统SSL证书转换(Openssl转换PFX)

在Linux中&#xff0c;你可以使用OpenSSL工具将PFX/P12格式的证书转换为单独的CRT&#xff08;证书&#xff09;、KEY&#xff08;私钥&#xff09;文件以及提取证书链 1. 提取私钥文件(.key) openssl pkcs12 -in your_certificate.pfx -nocerts -out private.key -nodes系统会…...

工业制造各个系统术语

简单总结下 文章目录 MES:制造执行系统ERP:企业资源计划PLM:产品生命周期管理MRP:物资需求计划QMS:质量管理系统APS:高级计划与排程SRM:供应商关系管理SCM:供应链管理CRM:客户关系管理WMS:仓库管理系统TMS:运输管理系统PMS:生产管理系统LES:物流执行系统FICO:财务与成本控制模块…...

深入解析:Python爬取Bilibili视频的技术创新与高阶实践

一、技术背景与挑战 Bilibili&#xff08;B站&#xff09;作为中国最大的泛二次元文化社区&#xff0c;其视频内容防护机制持续升级&#xff0c;传统爬虫技术面临三大核心挑战&#xff1a;动态加密参数、音视频分离存储、反爬策略多样化。本文提出一套融合AIGC辅助分析的智能爬…...

VS Code Markdown渲染配置

VS code markdown preview enhanced插件渲染配置 mac: commandshiftP命令输入Markdown Preview Enhanced: Customize CSS&#xff0c;并点击在打开的style.less配置文件添加一下配置 /* Please visit the URL below for more information: */ /* https://shd101wyy.github.…...

gcc -Wno-cpp

-Wno-cpp 是一个 GCC&#xff08;GNU 编译器&#xff09; 的编译选项&#xff0c;用来控制对 #warning 或 #error 指令中 # 注释的警告显示。 &#x1f31f; 简单解释&#xff1a; 在 C/C 代码中&#xff0c;有时候我们会看到这样的宏定义或注释&#xff1a; #warning This f…...

数据结构篇:线性表的另一表达—链表之单链表(上篇)

目录 1.链表的引入 1.1 链表的概念 1.2 next的意义 2.链表的分类 3.单链表的实现 3.1 单链表实现接口 3.1.1 插入节点函数封装 3.1.2 尾插 3.1.3 头插 3.1.4 报错的根本问题 3.1.5 头删 3.1.6 尾删 4.小结 1.链表的引入 根据顺序表的一些缺陷…...

SpringBoot企业级开发之【用户模块-获取用户详细信息】

接口文档的要求&#xff1a; 了解一下token令牌头是怎么用的 我们直接放到前端交互的controller类下&#xff0c;在声明的方法中加入参数为String token且加入注解RequestHeader(name"Authorization【你自己设定的token】") 设计思路: 实战开发&#xff1a; control…...

Mockito如何对静态方法进行测试

在 Mockito 中,直接对静态方法进行模拟是困难的,因为 Mockito 的设计理念是优先通过依赖注入(DI)管理对象,而静态方法破坏了这种设计(难以解耦)。不过,从 Mockito 3.4.0 版本开始,通过 mockStatic 方法支持了对静态方法的模拟(需配合 mockito-inline 依赖)。 从 Mo…...