go中用函数类型(如type eventhandler func(event interface{}))封装通知行为,替代接口继承,配合sync.rwmutex并发安全调用,通过独立注册表解耦observer与subject,避免channel分发的阻塞风险。

Go 里没有接口继承,怎么让 Observer 被统一管理
Go 不支持类和继承,所以不能像 Java 那样定义 Observer 接口再让各种实现去实现它。核心是用函数类型或接口封装“通知行为”,而不是靠类型继承关系。
推荐用函数类型:比如 type EventHandler func(event interface{})。它轻量、易组合、不强制绑定结构体,也避免了空接口泛滥带来的类型丢失问题。
- 如果事件类型固定(如只处理
UserCreated),直接用具体参数的函数更安全:type UserCreatedHandler func(u User) - 若需支持多种事件,可用带类型断言的
interface{},但务必在 handler 内做switch e := event.(type)判断,否则 panic 风险高 - 别用空接口切片存 handler(
[]interface{}),会丢失方法信息;用[]EventHandler这样的具名函数类型
如何安全地并发调用多个 Observer
观察者列表被多个 goroutine 同时增删或遍历时,不加保护必然出错——常见现象是 fatal error: concurrent map iteration and map write 或 panic 报 slice bounds out of range。
最简方案是读写都加 sync.RWMutex,但要注意:遍历前必须先 RLock,且不能在持有读锁时调用可能阻塞或重入 observer 的逻辑(比如某个 handler 又去调 Subscribe)。
- 订阅/退订操作用
Lock(),确保修改原子性 - 通知时用
RUnlock()释放读锁后,再逐个调 handler——避免锁住太久拖慢其他 goroutine - 如果 handler 可能长时间运行,考虑用
go handler(event)异步发,但得自己管好错误和资源回收
要不要用 channel 实现事件分发
channel 看起来很 Go,但实际在观察者模式里容易踩坑:一个 channel 对应一个 observer,意味着要为每个 subscriber 开 goroutine + channel,内存和调度开销明显上升;而且 channel 满了会阻塞 sender,违背“通知失败不应影响主体逻辑”的原则。
除非你明确需要背压控制(比如日志采样、限流上报),否则别用 channel 做分发主干。它更适合做“中间缓冲”或“跨组件解耦”,而不是替代简单的函数回调。
- channel 方案适合:事件量小、observer 固定、需严格顺序保证的场景(如配置热更新通知)
- 普通业务事件(如订单状态变更)用切片+函数调用更直接,性能更好,debug 更容易
- 若真要用 channel,别让 subject 直接往一堆 channel 里 send;改用一个中心 channel + 分发 goroutine,避免重复 copy event
为什么不要把 Observer 和 Subject 绑死在 struct 里
很多教程写一个 Subject struct 带 observers []Observer 字段,再配 Add/Notify 方法。这看似清晰,实则限制了复用:比如你想让一个 HTTP handler 同时当 observer 又当 subject,就得嵌套、指针转换,很快失控。
更好的做法是分离关注点——用独立的注册表(registry)管理映射关系,比如 map[string][]EventHandler,key 是事件名("user.deleted"),value 是 handler 列表。Subject 只负责触发,Observer 只负责响应,中间靠 registry 关联。
- 这样同一 handler 可监听多个事件,同一事件可被多个模块响应,扩展性更强
- 测试时可直接传入 mock handler,不用构造完整 struct 层级
- 注意 key 命名别用硬编码字符串,定义成常量:
const EventUserCreated = "user.created"
真正麻烦的是事件生命周期管理——谁负责清理已退出的 handler?Go 没有析构函数,得靠显式 Unsubscribe 或用 sync.WeakMap(1.22+)配合指针标识,这点很容易被忽略。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











