中介者模式在Go中通过struct+interface+显式注册解耦模块,避免硬引用;正确做法是中介者仅存储回调函数而非模块实例,用常量事件名和带缓冲channel实现轻量通信,不依赖第三方库以规避过度设计与panic风险。

中介者模式在 Go 中如何解耦模块间直接调用
Go 本身没有类继承和接口强制实现机制,但中介者模式依然有效——关键不是“模拟 Java 写法”,而是用 struct + interface + 显式注册来切断模块间的硬引用。模块不再互相持有对方指针,而是只依赖一个中介者接口,比如 Messenger 或 EventBus。
常见错误是让中介者直接持有所有模块实例(如 *UserModule、*OrderModule),这反而制造了新的中心依赖。正确做法是中介者只存回调函数或事件处理器,模块通过 RegisterHandler 注入行为,而非身份。
- 模块初始化时只传入
Messenger接口,不感知其他模块存在 - 中介者内部用
map[string][]func(interface{})管理事件通道,避免类型断言泛滥 - 事件名建议用常量定义(如
const EventOrderCreated = "order.created"),防止字符串散落
用 channel 实现轻量中介者的实际写法
比起复杂事件总线,多数 Go 项目更适合用带缓冲的 chan 做中介:一个中央 channel 收集事件,多个 goroutine 消费不同事件类型。这种方式零依赖、易测试、无反射开销。
注意别让中介 channel 成为性能瓶颈——不要把大结构体直接塞进 channel,而应传递 ID 或轻量指令;消费端自行查 DB 或调本地方法。
立即学习“go语言免费学习笔记(深入)”;
type Event struct {
Type string
Data interface{}
}
type Mediator struct {
events chan Event
}
func NewMediator() *Mediator {
return &Mediator{
events: make(chan Event, 100), // 缓冲防阻塞
}
}
func (m *Mediator) Publish(e Event) {
select {
case m.events <- e:
default:
// 可选:丢弃、告警或阻塞前检查
}
}
为什么不用第三方 EventBus 库
像 github.com/ThreeDotsLabs/watermill 或 github.com/asaskevich/EventBus 在 Go 中容易引发两个问题:一是过度设计(引入消息序列化、中间件、订阅树),二是运行时 panic 风险高(如重复订阅、未取消监听)。
《PHP设计模式》首先介绍了设计模式,讲述了设计模式的使用及重要性,并且详细说明了应用设计模式的场合。接下来,本书通过代码示例介绍了许多设计模式。最后,本书通过全面深入的案例分析说明了如何使用设计模式来计划新的应用程序,如何采用PHP语言编写这些模式,以及如何使用书中介绍的设计模式修正和重构已有的代码块。作者采用专业的、便于使用的格式来介绍相关的概念,自学成才的编程人员与经过更多正规培训的编程人员
真实项目里,90% 的模块通信只需「发一次、收一次」或「广播给固定几个 handler」。自己写的中介者能精确控制生命周期:
- 模块退出时显式调用
Unsubscribe(),避免 goroutine 泄漏 - 测试时可直接替换
events chan为nil或 mock channel,无需打桩 - 类型安全靠 Go 编译器保障,不用依赖
interface{}+reflect
模块间状态同步时的典型陷阱
中介者不解决状态一致性问题。例如订单模块发布 EventOrderPaid,库存模块消费后扣减库存——如果扣减失败,中介者不会自动重试或回滚。这必须由业务层决定:是发补偿事件?还是调用方兜底?
另一个高频坑是并发读写共享状态。中介者本身不加锁,若多个 handler 同时修改同一份内存(如全局 sync.Map),必须由 handler 自行加锁或改用原子操作。
更稳妥的做法是:中介者只负责「通知」,状态变更逻辑完全下沉到模块内部,模块通过本地事务保证一致性。









