Go语言使用事件系统实现事件的响应和处理
来源:云海天教程
时间:2022-12-31 09:59:30 157浏览 收藏
本篇文章向大家介绍《Go语言使用事件系统实现事件的响应和处理》,主要包括结构体,具有一定的参考价值,需要的朋友可以参考一下。
Go语言可以将类型的方法与普通函数视为一个概念,从而简化方法和函数混合作为回调类型时的复杂性。这个特性和 C# 中的代理(delegate)类似,调用者无须关心谁来支持调用,系统会自动处理是否调用普通函数或类型的方法。本节中,首先将用简单的例子了解 Go语言是如何将方法与函数视为一个概念,接着会实现一个事件系统,事件系统能有效地将事件触发与响应两端代码解耦。
方法和函数的统一调用
本节的例子将让一个结构体的方法(class.Do)的参数和一个普通函数(funcDo)的参数完全一致,也就是方法与函数的签名一致。然后使用与它们签名一致的函数变量(delegate)分别赋值方法与函数,接着调用它们,观察实际效果。详细实现请参考下面的代码。
package mainimport "fmt"// 声明一个结构体type class struct {}// 给结构体添加Do方法func (c *class) Do(v int) { fmt.Println("call method do:", v)}// 普通函数的Dofunc funcDo(v int) { fmt.Println("call function do:", v)}func main() { // 声明一个函数回调 var delegate func(int) // 创建结构体实例 c := new(class) // 将回调设为c的Do方法 delegate = c.Do // 调用 delegate(100) // 将回调设为普通函数 delegate = funcDo // 调用 delegate(100)}代码说明如下:第 10 行,为结构体添加一个 Do() 方法,参数为整型。这个方法的功能是打印提示和输入的参数值。第 16 行,声明一个普通函数,参数也是整型,功能是打印提示和输入的参数值。第 24 行,声明一个 delegate 的变量,类型为 func(int),与 funcDo 和 class 的 Do() 方法的参数一致。第 30 行,将 c.Do 作为值赋给 delegate 变量。第 33 行,调用 delegate() 函数,传入 100 的参数。此时会调用 c 实例的 Do() 方法。第 36 行,将 funcDo 赋值给 delegate。第 39 行,调用 delegate(),传入 100 的参数。此时会调用 funcDo() 方法。
运行代码,输出如下:
call method do: 100
call function do: 100
这段代码能运行的基础在于:无论是普通函数还是结构体的方法,只要它们的签名一致,与它们签名一致的函数变量就可以保存普通函数或是结构体方法。
了解了 Go语言的这一特性后,我们就可以将这个特性用在事件中。
事件系统基本原理
事件系统可以将事件派发者与事件处理者解耦。例如,网络底层可以生成各种事件,在网络连接上后,网络底层只需将事件派发出去,而不需要关心到底哪些代码来响应连接上的逻辑。或者再比如,你注册、关注或者订阅某“大V”的社交消息后,“大V”发生的任何事件都会通知你,但他并不用了解粉丝们是如何为她喝彩或者疯狂的。如下图所示为事件系统基本原理图。图:事件系统基本原理
一个事件系统拥有如下特性:能够实现事件的一方,可以根据事件 ID 或名字注册对应的事件。事件发起者,会根据注册信息通知这些注册者。一个事件可以有多个实现方响应。
通过下面的步骤详细了解事件系统的构成及使用。
事件注册
事件系统需要为外部提供一个注册入口。这个注册入口传入注册的事件名称和对应事件名称的响应函数,事件注册的过程就是将事件名称和响应函数关联并保存起来,详细实现请参考下面代码的 RegisterEvent() 函数。package main// 实例化一个通过字符串映射函数切片的mapvar eventByName = make(map[string][]func(interface{}))// 注册事件,提供事件名和回调函数func RegisterEvent(name string, callback func(interface{})) { // 通过名字查找事件列表 list := eventByName[name] // 在列表切片中添加函数 list = append(list, callback) // 将修改的事件列表切片保存回去 eventByName[name] = list}// 调用事件func CallEvent(name string, param interface{}) { // 通过名字找到事件列表 list := eventByName[name] // 遍历这个事件的所有回调 for _, callback := range list { // 传入参数调用回调 callback(param) }}代码说明如下:第 4 行,创建一个 map 实例,这个 map 通过事件名(string)关联回调列表([]func(interface{}),同一个事件名称可能存在多个事件回调,因此使用回调列表保存。回调的函数声明为 func(interface{})。第 7 行,提供给外部的通过事件名注册响应函数的入口。第 10 行,eventByName 通过事件名(name)进行查询,返回回调列表([]func(interface{})。第 13 行,为同一个事件名称在已经注册的事件回调的列表中再添加一个回调函数。第 16 行,将修改后的函数列表设置到 map 的对应事件名中。
拥有事件名和事件回调函数列表的关联关系后,就需要开始准备事件调用的入口了。
事件调用
事件调用方和注册方是事件处理中完全不同的两个角色。事件调用方是事发现场,负责将事件和事件发生的参数通过事件系统派发出去,而不关心事件到底由谁处理;事件注册方通过事件系统注册应该响应哪些事件及如何使用回调函数处理这些事件。事件调用的详细实现请参考上面代码的 CallEvent() 函数。代码说明如下:第 20 行,调用事件的入口,提供事件名称 name 和参数 param。事件的参数表示描述事件具体的细节,例如门打开的事件触发时,参数可以传入谁进来了。第 23 行,通过注册事件回调的 eventByName 和事件名字查询处理函数列表 list。第 26 行,遍历这个事件列表,如果没有找到对应的事件,list 将是一个空切片。第 29 行,将每个函数回调传入事件参数并调用,就会触发事件实现方的逻辑处理。
使用事件系统
例子中,在 main() 函数中调用事件系统的 CallEvent 生成 OnSkill 事件,这个事件有两个处理函数,一个是角色的 OnEvent() 方法,还有一个是函数 GlobalEvent(),详细代码实现过程请参考下面的代码。package mainimport "fmt"// 声明角色的结构体type Actor struct {}// 为角色添加一个事件处理函数func (a *Actor) OnEvent(param interface{}) { fmt.Println("actor event:", param)}// 全局事件func GlobalEvent(param interface{}) { fmt.Println("global event:", param)}func main() { // 实例化一个角色 a := new(Actor) // 注册名为OnSkill的回调 RegisterEvent("OnSkill", a.OnEvent) // 再次在OnSkill上注册全局事件 RegisterEvent("OnSkill", GlobalEvent) // 调用事件,所有注册的同名函数都会被调用 CallEvent("OnSkill", 100)}代码说明如下:第 6 行,声明一个角色的结构体。在游戏中,角色是常见的对象,本例中,角色也是 OnSkill 事件的响应处理方。第 10 行,为角色结构添加一个 OnEvent() 方法,这个方法拥有 param 参数,类型为 interface{},与事件系统的函数(func(interface{}))签名一致。第 16 行为全局事件响应函数。有时需要全局进行侦听或者处理一些事件,这里使用普通函数实现全局事件的处理。第 27 行,注册一个 OnSkill 事件,实现代码由 a 的 OnEvent 进行处理。也就是 Actor的OnEvent() 方法。第 30 行,注册一个 OnSkill 事件,实现代码由 GlobalEvent 进行处理,虽然注册的是同一个名字的事件,但前面注册的事件不会被覆盖,而是被添加到事件系统中,关联 OnSkill 事件的函数列表中。第 33 行,模拟处理事件,通过 CallEvent() 函数传入两个参数,第一个为事件名,第二个为处理函数的参数。
整个例子运行结果如下:
actor event: 100
global event: 100
结果演示,角色和全局的事件会按注册顺序顺序地触发。
一般来说,事件系统不保证同一个事件实现方多个函数列表中的调用顺序,事件系统认为所有实现函数都是平等的。也就是说,无论例子中的 a.OnEvent 先注册,还是 GlobalEvent() 函数先注册,最终谁先被调用,都是无所谓的,开发者不应该去关注和要求保证调用的顺序。
一个完善的事件系统还会提供移除单个和所有事件的方法。
好了,本文到此结束,带大家了解了《Go语言使用事件系统实现事件的响应和处理》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多Golang知识!
-
377 收藏
-
125 收藏
-
201 收藏
-
183 收藏
-
440 收藏
-
367 收藏
-
419 收藏
-
234 收藏
-
155 收藏
-
457 收藏
-
309 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 敏感的奇异果
- 赞 👍👍,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢up主分享文章!
- 2023-03-23 17:06:14
-
- 沉默的老鼠
- 很详细,已收藏,感谢博主的这篇博文,我会继续支持!
- 2023-03-11 10:58:02
-
- 聪明的书本
- 这篇技术贴真及时,太细致了,赞 👍👍,已加入收藏夹了,关注师傅了!希望师傅能多写Golang相关的文章。
- 2023-02-02 12:03:06
-
- 乐观的小蝴蝶
- 很详细,收藏了,感谢大佬的这篇技术文章,我会继续支持!
- 2023-01-24 15:24:31
-
- 欢喜的八宝粥
- 好细啊,码起来,感谢作者的这篇文章内容,我会继续支持!
- 2023-01-21 12:10:31
-
- 文艺的小土豆
- 这篇文章出现的刚刚好,作者加油!
- 2023-01-19 21:33:16
-
- 害怕的老虎
- 这篇技术文章太及时了,太细致了,真优秀,已收藏,关注大佬了!希望大佬能多写Golang相关的文章。
- 2023-01-15 17:35:24
-
- 快乐的星月
- 很好,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢大佬分享博文!
- 2023-01-14 05:42:06
-
- 聪明的眼睛
- 受益颇多,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢作者大大分享文章内容!
- 2023-01-12 05:13:18
-
- 舒服的天空
- 这篇文章真是及时雨啊,师傅加油!
- 2023-01-08 17:55:01
-
- 拉长的缘分
- 这篇技术贴出现的刚刚好,很详细,很有用,码住,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-01-06 09:36:47
-
- 俊逸的枕头
- 这篇博文真是及时雨啊,太详细了,很棒,码住,关注博主了!希望博主能多写Golang相关的文章。
- 2022-12-31 19:59:30