如何在Golang中实现观察者模式_Golang观察者模式事件通知示例

Go观察者模式需手动实现,核心是用sync.RWMutex+slice安全管理订阅者,通知时复制列表并goroutine并发调用,接口应轻量明确,生命周期管理防泄漏。

Go 语言没有内置的事件系统或接口继承机制,观察者模式必须手动实现;核心在于用 mapslice 管理订阅者,并确保通知时避免并发写 panic 和重复注册/移除问题。

sync.Map 存储观察者避免竞态

多个 goroutine 同时调用 Register / Unregister 时,普通 map 会 panic。虽然 sync.Map 不支持遍历,但可配合额外的 sync.RWMutex + slice 实现安全读写:

  • 只在注册/注销时加写锁,通知时用读锁遍历副本,性能更可控
  • 不要直接在通知循环中调用观察者方法——若某个观察者阻塞或 panic,会拖垮整个通知流
  • 推荐为每个观察者启动独立 goroutine(加 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 接口显式退出。