mediator 接口应按事件契约定义具体方法而非泛型notify,参与者只依赖最小行为接口(如broadcaster),状态管理交由专用组件,测试需用接口mock隔离外部依赖。

Mediator 接口定义要窄,别一上来就塞 Notify/Handle/Route
Go 没有抽象类和强制继承,Mediator 本质是个接口,但很多人照搬其他语言写法,定义成:type Mediator interface { Notify(sender interface{}, event string, data interface{}) }——这会导致所有参与者都得传 interface{},类型安全荡然无存,后期改起来像拆雷。
真实场景中,中介者协调的是**特定几类对象之间的有限交互**,比如 ChatRoom 里只有 User 发消息、NotificationService 推送、Logger 记录。应该按事件契约来定义方法:
func (m *ChatMediator) BroadcastMessage(from *User, content string)func (m *ChatMediator) LogEvent(level string, msg string)func (m *ChatMediator) SendPushTo(user *User, payload string)
这样每个方法参数明确、IDE 可跳转、单元测试可覆盖,也不会出现“传了个 *DBConn 却被 Notify 误收”的 runtime panic。
参与者不持有 Mediator 接口,只持具体实现指针
常见错误是让 User 持有 Mediator 接口变量:mediator Mediator,然后在构造时注入接口。问题在于:Go 的接口是隐式实现,一旦你后续给 ChatMediator 新增一个未被 User 使用的方法(比如 GetOnlineUsers()),编译不报错,但运行时可能因依赖未初始化而 panic;更糟的是,它掩盖了真实的依赖关系——User 其实只用到了广播能力,却“假装”能调用所有中介逻辑。
立即学习“go语言免费学习笔记(深入)”;
正确做法是让参与者只依赖它**真正需要的最小行为集**:
- 定义
type Broadcaster interface { Broadcast(from *User, msg string) } -
User字段改为broadcaster Broadcaster -
ChatMediator实现该接口,且仅暴露这个方法给User
这比“大接口+空实现”更符合 Go 的组合哲学,也避免了“接口膨胀后没人敢动”的僵化局面。
不要在 Mediator 里做状态管理,尤其别存 map[*User]chan
看到 Java/C# 示例里中介者缓存一堆对象引用、维护连接通道,有人会直接翻译成 Go:users map[string]*User + channels map[string]chan string。这在并发场景下极易出错——map 非线程安全,chan 关闭后写入 panic,且生命周期难对齐(用户下线了,channel 还卡在 map 里)。
中介者的职责是**转发与编排**,不是状态中心。正确姿势:
- 状态交给专门的
SessionManager或UserRegistry管理,它负责增删查、加锁、清理 -
ChatMediator只通过registry.GetOnlineUsers()拿当前列表,不自己 hold 任何*User指针 - 消息分发用
for _, u := range users { go u.Receive(msg) },而不是往一堆 channel 里塞——除非你真需要异步解耦,否则增加复杂度毫无必要
Go 的 goroutine 成本低,但滥用 channel 和 map 会把简单协作变成调度地狱。
测试 Mediator 时,用 interface mock,别碰 real User/Logger
写测试时直接 new 一个 *User 和 *ChatMediator,再 call BroadcastMessage,看着跑通了就提交?危险。因为 User 可能内部调用了网络、日志、数据库,一次测试失败可能是下游不稳,而非中介逻辑出错。
必须把依赖抽成接口并 mock:
- 定义
type Logger interface { Info(string) },让ChatMediator依赖它 - 测试里传入
mockLogger := &MockLogger{Logs: make([]string, 0)} - 断言
len(mockLogger.Logs) == 1 && mockLogger.Logs[0] == "msg sent"
同理处理推送服务、存储服务。否则你以为在测中介逻辑,其实是在测整个系统连通性——那已经不是 unit test,是 integration test,慢、不稳定、难定位。
中介模式的价值,在于把网状依赖压成星型结构;而它的脆弱点,恰恰藏在那些没被显式约束的隐式依赖里。接口粒度、状态归属、测试边界,三者漏掉任何一个,都会让“降低耦合”变成“制造新耦合”。










