中介者模式在Go中应通过接口+组合实现,同事对象不持有中介者指针,仅暴露Notify/Handle方法;中介者用sync.Map存储EventHandler回调,区分命令流(func(Command) error)与事件流(带缓冲channel)。

直接用中介者模式解决多对象通信开销,核心不是加一层转发,而是把“谁通知谁、谁响应谁”的路径收束到一个可控点上。Go 里尤其不能照搬 Java 那套继承写法——没有抽象类,也不靠强引用维系关系,关键在接口定义清晰、组合自然、通信流分类明确。
用接口+组合替代继承,同事对象不持有中介者指针
同事对象(比如 User、Order、Notification)只暴露行为方法,如 Notify(event Event) 或 Handle(cmd Command),内部不存 *Mediator 字段。中介者通过闭包、回调或注册函数接入它们:
- 定义
type EventHandler func(Event) error,同事注册时传入该函数,而非自身实例 - 中介者用
sync.Map存储 handler,key 用fmt.Sprintf("%p", &h)或自定义ID() string方法,避免同名类型冲突 - 同事注销时调用
Unregister(id string),幂等处理:查不到就忽略,不 panic
区分命令流与事件流,channel 使用要分场景
不是所有消息都适合走 channel。盲目用 chan Event 容易卡死或丢消息,根本在于没区分语义:
- 命令流(如 “扣减库存”“生成订单”)需要结果反馈 → 改用
func(Command) error回调,由中介者同步/异步执行并返回错误 - 事件流(如 “用户已登录”“订单已支付”)只需广播 → 用带缓冲的 channel(
make(chan Event, 16)),中介者启 goroutine 消费,配合select { case e := 防积压 - 绝对不用无缓冲 channel 让同事直接往
mediator.events <- e写 —— 一旦消费端阻塞或未启动,发送方立刻 hang 住
中介者本身不堆逻辑,只做路由与轻协调
中介者不是“上帝对象”,它的职责是收敛通信入口,而不是包办所有业务判断。复杂协调应下沉:
- 路由规则写成独立函数,如
routePaymentEvent(e PaymentEvent) []string,返回目标 handler ID 列表 - 状态校验、幂等控制、重试策略放在具体 handler 内部,中介者只负责按规则派发
- 跨模块事务(如“下单成功→扣库存→发通知”)用 Saga 模式拆解,中介者只触发第一步,后续靠事件链驱动
注册即生效,但需防竞态与重复
多个模块(用户、订单、风控)并发调用 Register() 时,容易 map 并发写 panic 或重复注册:
- 注册表必须用
sync.Map,或封装读写锁的结构体 - 注册前先检查是否已存在(用 handler 的唯一 ID 查),存在则跳过,不覆盖
- 提供
Replace(id string, h Handler)供热更新,旧 handler 自动注销

















