go中观察者模式应避免java式接口继承,推荐用channel+func(event)实现松耦合;切片方案需sync.rwmutex保护并拷贝遍历;channel方案须防goroutine泄漏,及时关闭。

Go 里没有 Observer 接口,别硬套 Java 写法
Go 没有内置的 Observer 或 Observable 类型,强行模仿 Java 的 register/unregister + notifyAll 结构,反而会让代码变重、难测、易漏通知。核心思路是:用 channel + interface 组合实现松耦合,而不是靠继承或接口强制约束。
- 观察者本质是「收到事件后执行某函数」,所以最轻量的观察者就是
func(Event)类型 - 被观察对象不需持有具体 observer 实例,只需维护一个
[]func(Event)切片或chan Event - 避免在通知逻辑里做耗时操作(比如 HTTP 请求),否则会阻塞整个事件流;该开 goroutine 就开
- 如果需要保证顺序且支持取消,优先用
chan Event+ select + context;若只图简单,切片遍历更直观
用 slice 存 handler 是最常用也最容易出错的方式
多数小项目直接用切片存回调函数,写起来快,但并发读写会 panic,且遍历时增删元素会出问题。
- 必须加
sync.RWMutex:读多写少场景下,RLock()读切片,Lock()写切片 - 遍历前要拷贝切片:
handlers := append([]func(Event){}, s.handlers...),否则可能 panic 或漏调用 - 注册/注销不要用值比较(比如
==两个匿名函数),要用指针或唯一 ID 标识观察者 - 示例注册逻辑:
s.mu.Lock(); defer s.mu.Unlock(); s.handlers = append(s.handlers, fn)
用 channel 实现异步解耦,但得小心 goroutine 泄漏
当事件产生频率高、处理逻辑重(如写 DB、发消息),channel 是更安全的选择,天然支持异步和背压,但容易忘记关闭或没消费完就退出。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 定义通道:
events chan Event,启动一个长期运行的 goroutine 消费:go func() { for e := range s.events { /* 处理 */ } }() - 务必在对象销毁时关闭 channel:
close(s.events),否则消费者 goroutine 永远卡在range - 如果观察者处理可能失败或超时,别让单个失败阻塞整个 channel;加
select { case - 不要给 channel 设过大的 buffer(比如
make(chan Event, 10000)),OOM 风险比阻塞还难排查
Event 类型设计不当会导致后期改不动
很多人一开始只传一个 string 或 map[string]interface{},结果加字段、改语义、做类型断言时全项目 grep 替换。
- 定义结构体
type UserCreatedEvent struct { UserID int64; Email string; CreatedAt time.Time },比裸interface{}更安全可读 - 避免用
json.RawMessage当万能字段——它绕过了编译检查,运行时才报错 - 如果真要支持多种事件,用 interface + 类型断言,但别嵌太深:
if e, ok := event.(UserCreatedEvent); ok { ... } - 事件结构体字段尽量导出(首字母大写),否则外部无法访问;但不要暴露内部状态(如 mutex、cache)
实际最难的不是写通逻辑,而是决定什么时候该同步通知、什么时候扔进 channel、以及 event 字段边界划在哪——这些没标准答案,得看你的错误容忍度、吞吐要求和团队对“事件”的共识。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










