观察者模式(Observer Pattern)
一、模式定义
观察者模式定义了对象之间一对多的依赖关系:当一个对象(被观察者 / Subject)的状态发生变化时,所有依赖于它的对象(观察者 / Observer)都会收到通知并自动更新。
打个比方:你关注了某个 UP 主,UP 主发新视频时你会收到推送。UP 主不需要认识每一个粉丝,只需要在发布时"广播"一下,关注了的人自然就收到了。
二、解决什么问题
- 耦合问题:如果一个对象的状态变化需要直接调用 N 个对象的更新方法,修改任何一个被调用方都会影响主体代码
- 动态订阅:运行时需要灵活地增减观察者,而不是在编译期就固定下来
- 广播通信:一个事件需要通知多个不相关的模块(日志、缓存、UI 刷新等),但事件源不应该知道这些模块的存在
三、核心角色
| 角色 | 职责 |
|---|---|
| Subject(被观察者) | 维护观察者列表,提供注册/注销/通知方法 |
| Observer(观察者接口) | 声明更新方法,所有具体观察者实现此接口 |
| ConcreteSubject | 持有具体状态,状态变化时调用 Notify 触发通知 |
| ConcreteObserver | 实现更新逻辑,从 Subject 获取自己需要的数据 |
四、结构图
┌─────────────────────┐ ┌──────────────────────┐
│ Subject │ │ Observer │
├─────────────────────┤ ├──────────────────────┤
│ - observers []Obs │ │ + Update(data) │
│ + Attach(o) │<──────│ │
│ + Detach(o) │ └──────────┬───────────┘
│ + Notify() │ │ implements
│ + GetState() State │ ┌──────────┴───────────┐
└─────────┬───────────┘ │ ConcreteObserver │
│ inherits │ - name string │
┌─────────┴───────────┐ │ + Update(data) │
│ ConcreteSubject │ └──────────────────────┘
│ - state string │
│ + SetState(s) │
└─────────────────────┘
五、Go 实现要点
Go 实现观察者模式有两个关键设计决策:
1. 推模式 vs 拉模式
- 推模式:Subject 在 Notify 时直接把变化的数据推给观察者。优点是观察者不用反查 Subject;缺点是不同观察者可能只需要部分数据
- 拉模式:Notify 只通知"有变化了",观察者自行调用 Subject.GetState() 获取所需数据。优点是按需取用;缺点是多一次调用
实际项目中推模式更常见,因为通知时 Subject 的状态可能已经变了,拉模式可能拿到的是最新状态而非触发通知时的状态。
2. 线程安全
Go 中观察者列表的增删和通知通常并发进行,必须用 sync.Mutex 或 sync.RWMutex 保护。注意死锁陷阱:在持锁状态下调用观察者的 Update 方法,如果 Update 反过来操作 Subject 就会死锁。解决方案是先拷贝观察者列表,释放锁后再遍历通知。
六、完整练习代码
package main
import (
"fmt"
"sync"
)
// ============================================================
// 观察者接口
// ============================================================
// Event 携带事件数据,推模式下由 Subject 传给 Observer
type Event struct {
Topic string // 事件主题标识
Payload interface{} // 事件携带的数据
}
// Observer 观察者接口,任何实现了 OnEvent 的类型都可以作为观察者
type Observer interface {
OnEvent(event Event)
}
// ============================================================
// 被观察者(Subject)
// ============================================================
// Subject 管理观察者列表,提供注册/注销/通知能力
type Subject struct {
mu sync.RWMutex
observers []Observer // 观察者列表
nextID int // 观察者ID分配器,用于注销时定位
observerID map[Observer]int
}
func NewSubject() *Subject {
return &Subject{
observerID: make(map[Observer]int),
}
}
// Attach 注册观察者,返回观察者ID用于后续注销
func (s *Subject) Attach(o Observer) int {
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
s.observers = append(s.observers, o)
s.observerID[o] = s.nextID
return s.nextID
}
// Detach 注销观察者
func (s *Subject) Detach(o Observer) {
s.mu.Lock()
defer s.mu.Unlock()
for i, obs := range s.observers {
if obs == o {
s.observers = append(s.observers[:i], s.observers[i+1:]...)
delete(s.observerID, o)
return
}
}
}
// Notify 广播事件给所有观察者
// 关键:先在锁内拷贝观察者列表,释放锁后再调用 OnEvent
// 这样即使 OnEvent 回调中操作 Subject(如 Attach/Detach)也不会死锁
func (s *Subject) Notify(event Event) {
s.mu.RLock()
snapshot := make([]Observer, len(s.observers))
copy(snapshot, s.observers)
s.mu.RUnlock()
for _, o := range snapshot {
o.OnEvent(event)
}
}
// ============================================================
// 具体观察者实现
// ============================================================
// 日志观察者:记录所有事件
type LoggerObserver struct {
name string
}
func NewLoggerObserver(name string) *LoggerObserver {
return &LoggerObserver{name: name}
}
func (l *LoggerObserver) OnEvent(event Event) {
fmt.Printf("[日志-%s] 收到事件: topic=%s, payload=%v\n",
l.name, event.Topic, event.Payload)
}
// 邮件观察者:只关注特定主题
type EmailObserver struct {
interestedTopic string
}
func NewEmailObserver(topic string) *EmailObserver {
return &EmailObserver{interestedTopic: topic}
}
func (e *EmailObserver) OnEvent(event Event) {
if event.Topic == e.interestedTopic {
fmt.Printf("[邮件] 发送告警邮件: %v\n", event.Payload)
}
}
// 统计观察者:统计事件数量
type MetricsObserver struct {
mu sync.Mutex
count map[string]int
}
func NewMetricsObserver() *MetricsObserver {
return &MetricsObserver{
count: make(map[string]int),
}
}
func (m *MetricsObserver) OnEvent(event Event) {
m.mu.Lock()
defer m.mu.Unlock()
m.count[event.Topic]++
fmt.Printf("[统计] 事件 %s 累计触发 %d 次\n", event.Topic, m.count[event.Topic])
}
// ============================================================
// 具体被观察者:订单服务
// ============================================================
type OrderService struct {
subject *Subject
}
func NewOrderService() *OrderService {
return &OrderService{subject: NewSubject()}
}
// Subscribe 让外部注册观察者
func (os *OrderService) Subscribe(o Observer) int {
return os.subject.Attach(o)
}
// Unsubscribe 注销观察者
func (os *OrderService) Unsubscribe(o Observer) {
os.subject.Detach(o)
}
// CreateOrder 下单后触发事件链
func (os *OrderService) CreateOrder(orderID string, amount float64) {
fmt.Printf("\n>>> 创建订单: %s, 金额: %.2f\n", orderID, amount)
// 1. 推送订单创建事件
os.subject.Notify(Event{
Topic: "order.created",
Payload: fmt.Sprintf("订单号=%s, 金额=%.2f", orderID, amount),
})
// 2. 如果金额超过阈值,触发风控事件
if amount > 10000 {
os.subject.Notify(Event{
Topic: "order.risk_alert",
Payload: fmt.Sprintf("大额订单告警: %s, 金额=%.2f", orderID, amount),
})
}
}
// ============================================================
// 主函数:演示观察者模式
// ============================================================
func main() {
// 创建订单服务(被观察者)
orderService := NewOrderService()
// 创建并注册观察者
logger := NewLoggerObserver("系统日志")
emailAlert := NewEmailObserver("order.risk_alert") // 只关心风控事件
metrics := NewMetricsObserver()
orderService.Subscribe(logger)
orderService.Subscribe(emailAlert)
orderService.Subscribe(metrics)
fmt.Println("========== 第一笔订单(小额)==========")
orderService.CreateOrder("ORD-001", 199.00)
fmt.Println("\n========== 第二笔订单(大额,触发风控)==========")
orderService.CreateOrder("ORD-002", 58000.00)
fmt.Println("\n========== 注销日志观察者后 ==========")
orderService.Unsubscribe(logger)
orderService.CreateOrder("ORD-003", 888.00)
fmt.Println("\n========== 最终统计 ==========")
fmt.Printf("各主题事件计数: %+v\n", metrics.count)
}
七、运行结果预期
========== 第一笔订单(小额)==========
>>> 创建订单: ORD-001, 金额: 199.00
[日志-系统日志] 收到事件: topic=order.created, payload=订单号=ORD-001, 金额=199.00
[统计] 事件 order.created 累计触发 1 次
========== 第二笔订单(大额,触发风控)==========
>>> 创建订单: ORD-002, 金额: 58000.00
[日志-系统日志] 收到事件: topic=order.created, payload=订单号=ORD-002, 金额=58000.00
[统计] 事件 order.created 累计触发 2 次
[日志-系统日志] 收到事件: topic=order.risk_alert, payload=大额订单告警: ORD-002, 金额=58000.00
[邮件] 发送告警邮件: 大额订单告警: ORD-002, 金额=58000.00
[统计] 事件 order.risk_alert 累计触发 1 次
========== 注销日志观察者后 ==========
>>> 创建订单: ORD-003, 金额: 888.00
[统计] 事件 order.created 累计触发 3 次
========== 最终统计 ==========
各主题事件计数: map[order.created:3 order.risk_alert:1]
八、关键设计决策解析
1. 为什么 Notify 要先拷贝再通知
如果在持锁状态下调用 OnEvent,而观察者内部又操作 Subject(比如某个观察者收到事件后注册新观察者),就会造成 sync.RWMutex 的重入问题导致死锁。拷贝列表后释放读锁,通知阶段不再持有锁,观察者可以安全地操作 Subject。
2. Event 结构体设计
用 Topic + Payload 的事件模型比直接传具体数据更灵活。观察者通过 Topic 过滤自己关心的事件,通过 Payload 获取数据。这本质上就是推模式——Subject 把变化推给所有观察者,观察者自行决定是否处理。
3. 与发布订阅模式的区别
| 维度 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 通信方式 | Subject 直接调用 Observer | 通过 Event Bus / Broker 中转 |
| 耦合度 | Subject 持有 Observer 引用 | 发布者和订阅者互不感知 |
| Go 实现 | 接口 + 切片 | channel 通信 |
| 适用场景 | 进程内事件通知 | 跨模块/跨进程消息分发 |
九、实际项目身影
- database/sql 连接池:连接状态变化通知
- Kubernetes 控制器:Watch 资源变更事件,Reconcile 循环响应
- Vue/React 响应式系统:数据变更 → 视图自动更新(本质就是观察者)
- Go 的
context.WithCancel:取消信号传播本质上也是观察者模式——父 context 取消时通知所有子 context
十、小结
观察者模式是解耦"事件源"和"事件处理"的经典手段。在 Go 中实现时,核心要点有三个:用接口定义观察者契约、用互斥锁保护观察者列表、用"先拷贝后通知"避免死锁。当观察者数量多且需要跨模块通信时,可以进一步演进为基于 channel 的发布订阅模式。
&spm=1001.2101.3001.5002&articleId=163231551&d=1&t=3&u=e449d710d852486b9c07c523a8fc50e4)

被折叠的 条评论
为什么被折叠?



