Go中中介者模式不用interface{},因会失去编译检查、引发运行时panic;应使用具体接口(如Participant)约束行为,通过结构体嵌入实现自注册,同步方法调用优于盲目用channel,测试需验证通知隔离性与状态变更。

中介者模式在 Go 里为什么不用 interface{} 或空接口
Go 没有类继承和抽象类,强行套用传统中介者 UML 图容易写出反模式代码。用 interface{} 做消息体或参与者类型,看似灵活,实则失去编译期检查,运行时 panic 风险高,调试时连是哪个 Send() 调用失败都难定位。
真正该做的是:用具体接口约束行为,让中介者只依赖契约,不依赖实现。
- 定义
Participant接口,含Notify()和SetMediator()方法,所有参与者必须实现 - 中介者持有一个
map[string]Participant,键为角色名(如"logger"、"notifier"),避免类型擦除 - 禁止在中介者内部做
if v, ok := p.(ConcreteLogger)类型断言——这等于又把耦合写回去了
如何用结构体嵌入实现参与者自动注册
手动调用 mediator.Register(p) 容易漏,尤其测试时新参与者一加就忘注册。更稳妥的做法是让参与者在初始化时“自注册”。
关键不是靠反射,而是利用 Go 的结构体嵌入 + 构造函数封装:
立即学习“go语言免费学习笔记(深入)”;
- 定义一个
baseParticipant匿名字段,含mediator Mediator和id string - 所有参与者结构体都嵌入它,例如
type User struct { baseParticipant } - 构造函数里完成注册:
func NewUser(m Mediator) *User { u := &User{}; u.baseParticipant = baseParticipant{mediator: m, id: "user"}; m.Register(u); return u } - 这样只要用了这个构造函数,注册就不可能遗漏;测试时也能轻松 mock
Mediator
消息传递用 channel 还是直接方法调用
看到“中介者”,很多人第一反应是加一层 chan Message 做异步解耦。但在 Go 中,90% 场景下这是过度设计。
同步方法调用更简单、可测、可控。channel 只应在以下情况引入:
- 消息需要排队或限流(如日志批量提交)
- 中介者本身要跨 goroutine 处理(如监听 HTTP 请求后广播给多个服务)
- 参与者响应耗时不可控,不能阻塞中介主流程
否则,直接调用 m.Notify("order_created", data) 更清晰。参数用结构体而非 map[string]interface{},字段名即契约,IDE 能跳转,编译器能报错。
测试中介者逻辑时最容易忽略的点
写单元测试时,常只验证“发了消息”,却漏掉“谁不该收到”。中介者的核心价值是控制交互边界,所以测试重点不是功能通路,而是隔离有效性。
- 给每个参与者打桩(stub),记录是否被调用、传入参数是否符合预期
- 显式测试“非目标参与者没被通知”:比如订单创建后,
"payment"收到通知,但"admin_alert"不应触发 - 测试中介者自身状态变更:如注册后
len(m.participants)是否正确,注销后是否真从 map 中删除 - 避免在测试里 sleep 等 channel,改用
sync.WaitGroup或带超时的select捕获 goroutine 行为
中介者真正的复杂点不在结构,而在消息路由规则的表达和演化——今天“用户注册”只通知邮件服务,明天可能加短信、加风控校验、加异步埋点。这些规则一旦散落在 if-else 里,就再也谈不上“降低耦合”了。










