如何使用Golang实现观察者模式_Golang观察者模式事件通知实现
发布时间 - 2026-02-01 00:00:00 点击率:次Go可用接口+map+互斥锁轻量实现观察者模式,Observer定义Update方法,Subject用map[string]Observer管理并支持Attach/Detach,Notify异步分发事件,需结合context或规范解绑防泄漏。
Go 语言没有内置的 Observer 接口或事件总线,但可以用接口 + 切片 + 方法绑定的方式轻量实现观察者模式,关键在于避免循环引用和确保通知时的并发安全。
用 Observer 接口和 Subject 结构体定义核心契约
观察者必须实现统一的通知方法,被观察者(Subject)则维护观察者列表并提供注册/移除/通知能力。不要用泛型约束观察者类型,否则会限制使用场景;用接口更灵活。
-
Observer接口只定义一个Update(event interface{})方法,接受任意事件数据 -
Subject是普通结构体,字段包含observers []Observer和互斥锁mu sync.RWMutex - 注册方法
Attach(o Observer)需加写锁,避免并发 append 导致 panic - 通知方法
Notify(event interface{})用读锁遍历,但注意:不能在遍历时修改observers切片
避免在 Notify 中同步调用观察者导致阻塞或死锁
如果某个 Observer.Update() 执行耗时或调用了 Detach(),同步遍历会卡住整个通知流,还可能引发递归修改切片的 panic。生产环境必须异步化。
- 在
Notify内启动 goroutine 分发事件,但需控制并发数,避免 goroutine 泛滥 - 更稳妥的做法是把事件推入 channel,由单独的 dispatcher goroutine 消费
- 若观察者数量少且逻辑简单(如日志、指标上报),可保留同步调用,但要明确标注“调用方需保证 Update 快速返回”
观察者注册时如何防止重复添加和内存泄漏
Go 没有对象身份比较(如 Java 的 ==),直接用 == 比较接口值不可靠。重复注册会导致同一观察者收到多次通知;不清理已失效的观察者(如 HTTP handler 关闭后未解绑)会造成内存泄漏。
- 不依赖地址比较,改用带唯
一 ID 的观察者(例如
type LoggerObserver struct { id string }),Attach前先遍历检查id - 提供
Detach(id string)显式解绑,比传入接口值更可控 - 对长生命周期的观察者(如全局 metrics collector),建议配合
context.Context实现自动注销:在Update中检测ctx.Err() != nil后主动调用Detach
type Observer interface {
Update(event interface{})
}
type Subject struct {
mu sync.RWMutex
observers map[string]Observer // 改用 map 便于按 id 查找/删除
}
func (s *Subject) Attach(id string, o Observer) {
s.mu.Lock()
defer s.mu.Unlock()
s.observers[id] = o
}
func (s *Subject) Detach(id string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.observers, id)
}
func (s *Subject) Notify(event interface{}) {
s.mu.RLock()
obs := make([]Observer, 0, len(s.observers))
for _, o := range s.observers {
obs = append(obs, o)
}
s.mu.RUnlock()
for _, o := range obs {
go o.Update(event) // 异步分发
}
}
真正难处理的是跨 goroutine 生命周期管理——比如一个 HTTP handler 注册为观察者,但 handler 返回后其闭包变量仍被 subject 持有。这时候光靠 Detach 不够,得结合 context 或 weak reference 思路(如用 sync.Map 存储带弱引用标记的观察者),但 Go 标准库不支持真正的弱引用,所以最实际的做法还是靠代码规范:谁注册,谁负责在合适时机解绑。
# java
# go
# golang
# app
# 代码规范
# 标准库
# String
# 结构体
# 递归
# 循环
# 接口
# Struct
# Interface
# Event
# 泛型
# 闭包
# 切片
# nil
# append
# map
# 并发
# channel
# 对象
# 事件
# 异步
# http
# 遍历
# 死锁
# 的是
# 互斥
# 可以用
# 能在
# 不支持
# 但要
# 则会
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
简历没回改:利用AI润色让你的文字更专业
Windows11怎样设置电源计划_Windows11电源计划调整攻略【指南】
百度输入法ai组件怎么删除 百度输入法ai组件移除工具
Laravel怎么实现微信登录_Laravel Socialite第三方登录集成
PHP怎么接收前端传的文件路径_处理文件路径参数接收方法【汇总】
PHP正则匹配日期和时间(时间戳转换)的实例代码
如何用VPS主机快速搭建个人网站?
如何挑选高效建站主机与优质域名?
如何用狗爹虚拟主机快速搭建网站?
如何自定义建站之星网站的导航菜单样式?
实例解析angularjs的filter过滤器
如何在Windows服务器上快速搭建网站?
北京网页设计制作网站有哪些,继续教育自动播放怎么设置?
电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?
Laravel观察者模式如何使用_Laravel Model Observer配置
Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】
Python面向对象测试方法_mock解析【教程】
Firefox Developer Edition开发者版本入口
如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环
如何快速搭建高效WAP手机网站吸引移动用户?
长沙企业网站制作哪家好,长沙水业集团官方网站?
详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)
如何快速生成专业多端适配建站电话?
Android自定义控件实现温度旋转按钮效果
JavaScript数据类型有哪些_如何准确判断一个变量的类型
谷歌浏览器下载文件时中断怎么办 Google Chrome下载管理修复
详解Huffman编码算法之Java实现
Laravel如何与Docker(Sail)协同开发?(环境搭建教程)
如何在IIS中新建站点并解决端口绑定冲突?
Laravel如何优化应用性能?(缓存和优化命令)
如何在阿里云虚拟主机上快速搭建个人网站?
今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】
Laravel如何实现密码重置功能_Laravel密码找回与重置流程
如何在建站宝盒中设置产品搜索功能?
电视网站制作tvbox接口,云海电视怎样自定义添加电视源?
JavaScript如何实现音频处理_Web Audio API如何工作?
如何在万网ECS上快速搭建专属网站?
如何使用 Go 正则表达式精准提取括号内首个纯字母标识符(忽略数字与嵌套)
黑客如何通过漏洞一步步攻陷网站服务器?
Laravel模型事件有哪些_Laravel Model Event生命周期详解
php json中文编码为null的解决办法
Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】
高性价比服务器租赁——企业级配置与24小时运维服务
微信推文制作网站有哪些,怎么做微信推文,急?
如何在橙子建站中快速调整背景颜色?
Laravel如何使用Service Container和依赖注入?(代码示例)
Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制
什么是javascript作用域_全局和局部作用域有什么区别?
浅谈Javascript中的Label语句
制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?


