Go语言中介者模式应避免类继承和接口强制实现,改用函数类型注册、channel异步事件总线及依赖注入组合,实现模块零import解耦。

Go 语言没有类继承和接口强制实现,直接照搬传统面向对象的中介者模式容易水土不服——关键不在于“写个 Mediator 结构体”,而在于用好 interface{}、函数值、通道和组合,让模块只依赖抽象协调逻辑,不互相 import。
用函数类型替代抽象中介者接口
在 Go 里硬套 Java 风格的 Mediator 接口(如 Notify(sender, event interface{}))会导致调用方仍需知道中介者存在。更轻量的做法是把协调逻辑定义为函数类型,由模块在初始化时注册:
type EventHandler func(event interface{})
var eventBus = make(map[string][]EventHandler)
func Register(topic string, handler EventHandler) {
eventBus[topic] = append(eventBus[topic], handler)
}
func Publish(topic string, event interface{}) {
for _, h := range eventBus[topic] {
h(event)
}
}
这样,OrderService 和 InventoryService 只需调用 Publish("order.created", order),完全不知道谁在监听,也不需要导入对方包。
立即学习“go语言免费学习笔记(深入)”;
用 channel + goroutine 实现解耦的异步协调
同步回调仍有隐式依赖(比如 handler panic 会中断发布流程)。用通道做事件总线,天然支持异步、背压和错误隔离:
type Event struct {
Topic string
Data interface{}
}
var eventCh = make(chan Event, 100)
func StartEventLoop() {
go func() {
for e := range eventCh {
switch e.Topic {
case "payment.succeeded":
go notifyShipping(e.Data)
go updateAnalytics(e.Data)
case "inventory.low":
go triggerReorder(e.Data)
}
}
}()
}
func Emit(topic string, data interface{}) {
select {
case eventCh <- Event{Topic: topic, Data: data}:
default:
// 可选:丢弃或打日志,避免阻塞调用方
}
}
-
Emit()调用方完全不感知下游逻辑,也不依赖任何服务实例 - 每个 handler 是独立 goroutine,一个 panic 不影响其他
- 模块间零 import:支付模块发事件,物流模块自己
go notifyShipping,二者代码文件互不引用
组合 mediator 结构体时避免循环 import
如果确实需要一个集中管理的 Coordinator 结构体(比如要聚合状态、重试策略),切忌让各业务模块直接持有一个 *Coordinator 指针——这等于又绑死了依赖。正确做法是:
- 定义纯行为接口:
type Notifier interface { SendEmail(to, body string) error } -
Coordinator实现该接口,但业务模块只依赖Notifier,不 import coordinator 包 - 启动时用构造函数注入:
orderSvc := NewOrderService(coordinator),而非在 order.go 里 import coordinator
这样,order.go 和 coordinator.go 之间没有 import 循环,编译期就切断了耦合。
真正难的不是写出中介者,而是克制住“我想控制一切”的冲动——Go 的解耦效果,往往取决于你愿意把多少协调逻辑推给 caller(函数参数)、多少交给 runtime(channel/goroutine)、多少留给启动时组合(依赖注入)。越早放弃“统一调度中心”的执念,模块就越松。










