事件总线EventBus

news2025/7/13 10:12:08

事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

什么是“总线”:一个集中式的事件处理机制。同时服务多个事件和多个观察者。相当于一个介于Publisher和Subscriber中间的桥梁。它隔离了Publlisher和Subscriber之间的直接依赖,接管了所有事件的发布和订阅逻辑,并负责事件的中转。

(图片源自:事件总线知多少(1) - 简书) 


实现方式:

1,EventBus维护一个事件类型的字典,发布者、订阅者在事件总线中获取此字典并执行发布、订阅操作(维护一个事件源与事件处理的映射字典)。
2,通过单例模式,确保事件总线的唯一入口
3,提供统一的事件注册、取消注册和触发(发布)方法

代码实现:

发布,订阅都是基于事件。首先我们定义事件接口,泛型接口(规定了事件参数类型)和具体的事件。

        /// <summary>
		/// 事件源
		/// 所有被订阅的事件都要继承这个
		/// </summary>
		public abstract class IEvent
		{
				/// <summary>
				/// 
				/// </summary>
				public string EventName;
		}

		public abstract class IEvent<T>: IEvent where T : MyEventArgs
		{
		}

        public class EventA :IEvent<MyEventArgsA>
		{
		}

        public class EventB : IEvent<MyEventArgsB>
		{
		}

事件参数

        public class MyEventArgs:EventArgs
		{
				/// <summary>
				/// 事件发生的时间
				/// </summary>
				public DateTime DateTime { get; set; }
		}

        /// <summary>
		/// 事件A的事件参数
		/// </summary>
		public class MyEventArgsA:MyEventArgs
		{
				 
		}

        /// <summary>
		/// 事件B的事件参数
		/// </summary>
		public class MyEventArgsB:MyEventArgs
		{
				 
		}

发布者:

不一定要特别定义一个Publisher。任何人任何时候都可以发布一个事件。只要发生事件并且发布到事件总线中即可。EventB就没有发布者,直接在program中发布 。

        public interface IPublisher<T> where T:IEvent
		{
				/// <summary>
				/// 发布事件
				/// </summary>
				void Pub();
		}

        /// <summary>
		/// 发布者
		/// 
		/// </summary>
		public class PublisherA : IPublisher<EventA>
		{
				/// <summary>
				/// 发布事件
				/// </summary>
				public void Pub()
				{
						//创建事件并发布
						EventBus.Default.Publish(new EventA() { EventName = "我是EventA" }
						, this
						, new MyEventArgsA() { DateTime = DateTime.Now });
				}
		}

 订阅者:

        /// <summary>
		/// 观察者(订阅者)接口
		/// 规范了订阅的事件类型
		/// </summary>
		public abstract class ISubscriber<IEvent>
		{
				public abstract void Subscribe(Action<object, MyEventArgs> eventHandler);

		}

        public class SubscriberA : ISubscriber<EventA>
		{
				/// <summary>
				/// 订阅者应该知道自己要执行什么。当然也可以从外部修改
				/// </summary>
				public Action<object, MyEventArgs> eventHandler = (sender, eventArgs) =>
				{
						Console.WriteLine("ScbscriberA Handler   sender:" + sender.ToString() + "    eventArgs DateTime:" + eventArgs.DateTime.ToString());
				};

				public void Subscribe()
				{
						EventBus.Default.Subscribe<EventA>(eventHandler);
				}
				public override void Subscribe(Action<object, MyEventArgs> eventHandler)
				{
						EventBus.Default.Subscribe<EventA>(eventHandler);
				}
		}

        public class SubscriberB : ISubscriber<EventB>
		{
				/// <summary>
				/// 订阅者应该知道自己要执行什么。所以这里直接定义了一个Action.
				/// 当然也可以从外部传入Action
				/// </summary>
				public Action<object, MyEventArgs> eventHandler = (sender, eventArgs) =>
				{
						Console.WriteLine("ScbscriberB Handler   sender:" + sender.ToString() + "    eventArgs DateTime:" + eventArgs.DateTime.ToString());
				};

				public void Subscribe()
				{
						EventBus.Default.Subscribe<EventB>(eventHandler);
				}
				public override void Subscribe(Action<object, MyEventArgs> eventHandler) 
				{
						EventBus.Default.Subscribe<EventB>(eventHandler);
				}
				 
		}

终于到事件总线了。

        public class EventBus
		{
        private static EventBus _default;
        protected static readonly object locker = new object();
        private Dictionary<Type, List<Action<object, MyEventArgs>>> eventDic = new Dictionary<Type, List<Action<object, MyEventArgs>>>();
        public static EventBus Default
        {
            get
            {
                if (_default == null)
                {
                    lock (locker)
                    {
                        // 如果类的实例不存在则创建,否则直接返回
                        if (_default == null)
                        {
                            _default = new EventBus();
                        }
                    }
                }
                return _default;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">订阅的时候,就应该知道订阅什么事件</typeparam>
        /// <param name="eventHandler"></param>
        public void Subscribe<T>( Action<object, MyEventArgs> eventHandler) where T:IEvent
        {
            lock (locker)
            {
                if (eventDic.ContainsKey(typeof(T)))
                {
                    eventDic[typeof(T)].Add(eventHandler);
								}
								else
								{
                    eventDic.Add(typeof(T), new List<Action<object, MyEventArgs>>() { eventHandler });
                }
            }
        }

        public void Unsubscribe<T>(Action<object, MyEventArgs> eventHandler) where T : IEvent
        {
            lock (locker)
            {
                if (eventDic.ContainsKey(typeof(T)) && eventDic[typeof(T)].Contains(eventHandler))
                {
                    eventDic[typeof(T)].Remove(eventHandler);
                }
            }
        }

        /// <summary>
        /// 发布
        /// </summary>
        /// <param name="e">发布的时候,应该是发布具体的事件,而不是事件类型</param>
        /// <param name="sender"></param>
        /// <param name="eventArgs"></param>
        public virtual void Publish(IEvent e,object sender, MyEventArgs eventArgs) 
        {
            lock (locker)
            {
								if (eventDic.ContainsKey(e.GetType()))
								{
                    var subscriptions = eventDic[e.GetType()];
                    for (int i = 0; i < subscriptions.Count; i++)
                    {
                        subscriptions[i](sender, eventArgs);
                    }
                }
                
            }
        }
    }

eventDic就是上面说的维护的事件类型字典。记录了每个事件类型对应的要执行的所有Action。

EventBus全局唯一实例,这里使用Default实例。

有订阅,取消订阅,发布方法。也有用反射自动实现订阅的。

 

最后就是调用

        class Program
		{
				static void Main(string[] args)
				{
						
						SubscriberA suba = new SubscriberA();
						suba.Subscribe();
						PublisherA puba = new PublisherA();
						puba.Pub();


						SubscriberB subb = new SubscriberB();
						subb.Subscribe();
						//创建并发布事件
						EventBus.Default.Publish(new EventB() { EventName = "我是EventB" }
						, "Program"
						, new MyEventArgsA() { DateTime = DateTime.Now });
				}
		}

输出

ScbscriberA Handler   sender:MyEventBus.PublisherA    eventArgs DateTime:2022/11/19 10:01:00
ScbscriberB Handler   sender:Program    eventArgs DateTime:2022/11/19 10:01:00

比较好的讲事件总线的文章

什么是事件总线 - 走看看

 详解c# 事件总线

过程中遇到一个问题:

类型M继承于N,但是Action<M>无法转换为Action<N>。

原因如下
例如
        Action<string> a = (x) => {   
            Console.WriteLine(x.Length);
        };

        Action<object> b = a;//假如Action<string>可以转换为Action<object>。

        b(new object())//无法执行,object无Length属性

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

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

相关文章

C#编程深入研究变量,类型和方法

编写正确的C#代码 简单的调试技术 变量的语法 声明类型和值 仅声明类型 访问修饰符 使用类型 通用内置类型 类型转换 推断式声明 自定义类型 类型综述 命名变量 变量的作用域 运算符 定义方法 指定参数 指定返回值 常见的Unity方法 Start方法 Update方法 …

金山云:基于 JuiceFS 的 Elasticsearch 温冷热数据管理实践

01 Elasticsearch 广泛使用带来的成本问题 Elasticsearch&#xff08;下文简称“ES”&#xff09;是一个分布式的搜索引擎&#xff0c;还可作为分布式数据库来使用&#xff0c;常用于日志处理、分析和搜索等场景&#xff1b;在运维排障层面&#xff0c;ES 组成的 ELK&#xff…

MMDetection3D库中的一些模块介绍

本文目前仅包含2个体素编码器、2个中间编码器、1个主干网络、1个颈部网络和1个检测头。如果有机会&#xff0c;会继续补充更多模型。 若发现内容有误&#xff0c;欢迎指出。 MMDetection3D的点云数据一般会经历如下步骤/模块&#xff1a; #mermaid-svg-q9Wy2NQvFHfuPWKs {font-…

骨传导原理是什么,佩戴骨传导耳机的过程中对于耳道有无损害

随着新时代的到来&#xff0c;我们周围的数码产品逐渐被新产物所替代&#xff0c;以往在耳机市面上&#xff0c;普遍都是入耳式耳机&#xff0c;但长时间佩戴这种耳机的话对于我们耳道来说是有着不可逆的伤害&#xff0c;而在近几年骨传导耳机的出现&#xff0c;打破了传统耳机…

18.Redis系列之AOF方式持久化

本文学习redis7两大持久化技术之一&#xff1a;AOF&#xff08;Append Only File&#xff09;日志追加方式持久化备份与还原&#xff0c;重写以及AOF方式的优缺点 1. AOF相关配置 首先我们先简单了解下Redis7中AOF相关配置 // 开启AOF方式持久化&#xff0c;默认no appendon…

基于真实场景解读 K8s Pod 的各种异常

在 K8s 中&#xff0c;Pod 作为工作负载的运行载体&#xff0c;是最为核心的一个资源对象。Pod 具有复杂的生命周期&#xff0c;在其生命周期的每一个阶段&#xff0c;可能发生多种不同的异常情况。K8s 作为一个复杂系统&#xff0c;异常诊断往往要求强大的知识和经验储备。结合…

骚戴独家笔试---SQL笔试

SQL笔试训练 查询结果去重 两种答案 查找某个年龄段的用户信息 查找除复旦大学的用户信息 三种答案 用where过滤空值练习 三种答案 查询NULL时&#xff0c;不能使用比较运算符(或者< >)&#xff0c;需要使用IS NULL运算符或者IS NOT NULL运算符。 操作符混合运用 我这里…

力扣 792. 匹配子序列的单词数

题目 给定字符串 s 和字符串数组 words, 返回 words[i] 中是s的子序列的单词个数 。 字符串的 子序列 是从原始字符串中生成的新字符串&#xff0c;可以从中删去一些字符(可以是none)&#xff0c;而不改变其余字符的相对顺序。 例如&#xff0c; “ace” 是 “abcde” 的子序…

java spring引用外部jar包并使用

spring引用外部jar包并使用1、将jar包放到src/main/resources/lib2、编辑pom.xml文件build下面加入resources&#xff0c;不加话的打包会找不到资源3、project structure中引入该lib1、将jar包放到src/main/resources/lib 2、编辑pom.xml文件 打开pom文件&#xff0c;找到相应…

计算机网络基本知识

计算机网络基本知识 计算机网络定义&#xff1a;是一个将分散的、具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功能完善的软件实现资源共享和信息传递的系统。 1.1计算机网络在信息时代作用 1.2因特网概述 1.2.1网络、互联网、因特网 网…

DeepLab V1学习笔记

DeepLab V1摘要相关的工作遇到的问题和解决的方法信号下采样空间不变性(spatial insensitivity/invariance)论文的优点(贡献)网络的模型空洞卷积CRF多尺度预测模型总结实验结果Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs论文地址 : D…

[附源码]java毕业设计乒乓球俱乐部管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【光学】基于Matlab模拟干涉条纹图

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

亿级万物互联新时代的物联网消息中间件EMQX调研

简介 最近去某餐厅吃饭&#xff0c;进门时智能门自动打开房门同时来一句"欢迎光临"&#xff0c;然后伸手到门口的洗手台&#xff0c;水管无接触自动出水&#xff0c;端起菜盘走向台子选择自己喜欢的菜品&#xff0c;最后将菜盘放在智能结账机上&#xff0c;智能结账…

代码随想录算法训练营第三天|LeetCode 203.移除链表元素 、707.设计链表 、206.反转链表

LeetCode 203.移除链表元素 题目链接&#xff1a;203.移除链表元素 链表的定义&#xff1a; // 单链表 struct ListNode {int val; // 节点上存储的元素ListNode *next; // 指向下一个节点的指针ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数 };ListNode(i…

数据结构实验教程-第一套

1&#xff0e;在平衡二叉树中插入一个结点后造成了不平衡&#xff0c;设最低的不平衡结点为A&#xff0c;并已知A的左孩子的平衡因子为1&#xff0c;右孩子的平衡因子为0&#xff0c;则应作_型调整以使其平衡。 A.LL B.LR C.RL D.RR答案为a&#xff0c;错选了c。 平衡因子 左子…

model.py篇

model.py篇 目录如下&#xff1a; 引言找LeNet5网络结构书写代码测试结果函数解释 引言 卷积主要用于特征的提取&#xff0c;而model.py则是为了从输入信息中筛选出我们需要的信息。 我们在阅读完论文后&#xff0c;对我们需要的模型进行搭建&#xff0c;下以LeNet5的model…

子域名访问计数(哈希表、字符串、索引)

力扣地址&#xff1a;力扣 网站域名 "discuss.leetcode.com" 由多个子域名组成。顶级域名为 "com" &#xff0c;二级域名为 "leetcode.com" &#xff0c;最低一级为 "discuss.leetcode.com" 。当访问域名 "discuss.leetcode.com&…

【Struts2】idea快速搭建struts2框架

文章目录什么是SSH框架&#xff1f;Struts2框架1、struts2的环境搭建1.1 创建web项目&#xff08;maven&#xff09;&#xff0c;导入struts2核心jar包1.2 配置web.xml&#xff08;过滤器&#xff09;&#xff0c;是struts2的入口&#xff0c;先进入1.3 创建核心配置文件struts…

力扣(LeetCode)13. 罗马数字转整数(C++)

模拟 罗马数字和掰手指数数的区别在于&#xff0c;IV/IXIV/IXIV/IX 这类倒着数数的&#xff0c;和阿拉伯数字最大的区别在于 555 的 10k10^k10k 倍 k∈Nk\isin Nk∈N &#xff0c;需要被表示出来。所以除了记录 I/X/C/MI/X/C/MI/X/C/M ——1/10/100/10001/10/100/10001/10/100…