0

0

Golang观察者模式实现事件通知机制

P粉602998670

P粉602998670

发布时间:2026-01-08 14:12:03

|

437人浏览过

|

来源于php中文网

原创

Go中不用interface{}做事件参数,因会丢失类型信息导致运行时panic且IDE无法推导;应为每类事件定义具体结构体,编译期校验。

golang观察者模式实现事件通知机制

为什么 Go 里不用 interface{} 做事件参数

interface{} 接收事件数据看似灵活,实际会丢失类型信息,导致通知回调里必须做类型断言,一旦出错就 panic。更麻烦的是 IDE 和静态检查完全无法推导参数结构,协作和维护成本陡增。

推荐做法是为每类事件定义具体结构体,比如:

type UserCreatedEvent struct {
    UserID   int64
    Email    string
    CreatedAt time.Time
}

观察者注册时明确声明自己关心哪种事件,发布方也只发对应类型——编译期就能校验,不靠运行时碰运气。

如何避免 Observer 注册后内存泄漏

Go 没有析构函数,Observer 如果持有外部对象引用(比如 HTTP handler、DB 连接),又没显式注销,就会让整个对象图无法被 GC 回收。

立即学习go语言免费学习笔记(深入)”;

  • 注册时返回一个 func() 取消函数,调用即从内部 map/slice 中移除该 observer
  • Observer 实现 io.Closer 接口,统一用 defer obs.Close() 管理生命周期
  • sync.Map 存储 observer,key 用 uintptr(unsafe.Pointer(&observer)) 需谨慎——更稳妥是用自增 ID 或 UUID 作 key

并发安全的 Notify 实现要注意什么

Notify() 被多个 goroutine 同时调用,而 observer 列表可能正在被增删,直接遍历 slice 会 panic。

百度文心百中
百度文心百中

百度大模型语义搜索体验中心

下载
  • 读多写少场景:用 sync.RWMutex,Notify 用 RLock(),注册/注销用 Lock()
  • 写频繁场景:改用 sync.Map 存 observer,但注意 Range() 不保证原子性,需配合 channel + goroutine 分发事件
  • 关键点:不要在锁内执行 observer 回调——防止某个 observer 卡住阻塞全部通知
func (e *EventBus) Notify(event interface{}) {
    e.mu.RLock()
    obs := make([]Observer, 0, len(e.observers))
    for _, o := range e.observers {
        obs = append(obs, o)
    }
    e.mu.RUnlock()

    for _, o := range obs {
        go o.OnEvent(event) // 异步调用,不阻塞主流程
    }
}

用泛型简化不同事件类型的注册与分发

Go 1.18+ 可以用泛型避免为每种事件写重复的 SubscribeUserCreatedSubscribeOrderPaid 方法。

核心是把事件类型作为类型参数,让编译器生成专用版本:

type EventHandler[T any] func(T)

type EventBus struct {
    handlers sync.Map // key: reflect.Type, value: []EventHandler[T]
}

func (eb *EventBus) Subscribe[T any](handler EventHandler[T]) {
    t := reflect.TypeOf((*T)(nil)).Elem()
    if handlers, ok := eb.handlers.Load(t); ok {
        eb.handlers.Store(t, append(handlers.([]EventHandler[T]), handler))
    } else {
        eb.handlers.Store(t, []EventHandler[T]{handler})
    }
}

func (eb *EventBus) Publish[T any](event T) {
    t := reflect.TypeOf((*T)(nil)).Elem()
    if handlers, ok := eb.handlers.Load(t); ok {
        for _, h := range handlers.([]EventHandler[T]) {
            h(event)
        }
    }
}

调用时:bus.Subscribe[UserCreatedEvent](func(e UserCreatedEvent) { ... }),类型安全,无反射开销,IDE 自动补全也正常。

真正难的不是写完这个模式,而是想清楚哪些事件该同步通知、哪些必须异步、哪些需要重试或持久化——这些决策不会出现在代码模板里,但决定了系统是否可靠。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

177

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

226

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

336

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

388

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

189

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

191

2025.06.17

Golang 分布式缓存与高可用架构
Golang 分布式缓存与高可用架构

本专题系统讲解 Golang 在分布式缓存与高可用系统中的应用,涵盖缓存设计原理、Redis/Etcd集成、数据一致性与过期策略、分布式锁、缓存穿透/雪崩/击穿解决方案,以及高可用架构设计。通过实战案例,帮助开发者掌握 如何使用 Go 构建稳定、高性能的分布式缓存系统,提升大型系统的响应速度与可靠性。

27

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.5万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号