

新闻资讯
技术学院Go观察者模式需手动实现,核心是用sync.RWMutex+slice安全管理订阅者,通知时复制列表并goroutine并发调用,接口应轻量明确,生命周期管理防泄漏。
Go 语言没有内置的事件系统或接口继承机制,观察者模式必须手动实现;核心在于用 map 或 管理订阅者,并确保通知时避免并发写 panic 和重复注册/移除问题。
slice
sync.Map 存储观察者避免竞态多个 goroutine 同时调用 Register / Unregister 时,普通 map 会 panic。虽然 sync.Map 不支持遍历,但可配合额外的 sync.RWMutex + slice 实现安全读写:
recover),但需注意资源泄漏风险Observer 接口定义要轻量且明确Go 中接口越小越好。观察者只需一个方法,参数应包含事件类型和有效载荷,不建议传入发布者实例(破坏解耦):
type Event string
const (
EventUserCreated Event = "user_created"
EventOrderPaid Event = "order_paid"
)
type Observer interface {
OnEvent(event Event, data interface{})
}
data interface{} 提供灵活性,但调用方需保证类型一致,否则运行时报错Update() 这类模糊方法名,无法体现事件语义Event 字段,而非靠接口方法重载不能边遍历边修改切片,否则可能漏通知或 panic。典型做法是:读锁保护下拷贝当前列表,释放锁后再逐个通知:
func (e *EventBus) Notify(event Event, data interface{}) {
e.mu.RLock()
observers := make([]Observer, len(e.observers))
copy(observers, e.observers)
e.mu.RUnlock()
for _, obs := range observers {
go func(o Observer) {
defer func() {
if r := recover(); r != nil {
log.Printf("observer panic: %v", r)
}
}()
o.OnEvent(event, data)
}(obs)
}
}
copy() 前必须已加读锁,否则可能拷贝到部分更新状态go func(o Observer) 而非直接 obs,避免循环变量复用问题go 并移除 recover,但需接受单点失败影响全局真正难的不是注册和通知逻辑,而是生命周期管理:观察者何时该被自动清理?比如 HTTP handler 注册了观察者,但 handler 返回后没人调用 Unregister,就会导致内存泄露。这种场景下,考虑用 context.Context 关联取消信号,或让观察者实现 io.Closer 接口显式退出。