在复杂业务系统中,多个模块或结构体之间往往需要互相调用。如果让它们直接持有彼此的引用,代码会变成一张错综复杂的网,任何一处改动都可能引发连锁问题。中介者模式的核心思想,是引入一个独立的中介者来封装对象之间的交互规则,使原本互相依赖的对象只依赖于中介者,从而实现解耦。

中介者模式的基本原理
中介者模式属于行为型设计模式,它定义了一个中介对象来封装一系列对象之间的交互。在没有中介者时,对象A可能直接调用对象B和C的方法;引入中介者后,A、B、C都只与中介者通信,由中介者决定如何转发或协调。这样对象之间的耦合从多对多变成一对多,维护成本显著降低。
在Golang中,我们通常使用接口来定义中介者的行为,用结构体实现该接口。各个同事(Colleague)结构体内部保存一个中介者接口的实例,当自身状态变化或需要其他同事配合时,就调用中介者的方法,而不是直接操作其他同事。这种方式非常契合Go的隐式接口实现机制,不需要显式继承。
定义中介者与同事接口
我们先从接口设计开始。中介者接口一般提供供同事调用的通知方法,同事接口则可以抽象出接收消息的行为。下面的代码展示了最基础的定义方式。
package main
// Mediator 定义中介者接口
type Mediator interface {
Notify(sender Colleague, event string)
}
// Colleague 定义同事接口
type Colleague interface {
SetMediator(m Mediator)
OnNotify(event string)
}
这里的Notify方法由同事调用,把自身和事件名传给中介者;OnNotify则用于中介者回调同事。注意在正文描述中提到的HTML标签如<input>不会出现在Go代码里,我们只是借用接口完成对象解耦。
接口划分清楚后,具体类型只需要关注自身逻辑。比如一个按钮同事和一个文本框同事,都不需要知道对方存在,只管向中介者发通知。这种边界划分让单元测试也更容易,我们可以用伪中介者来验证单个同事的行为。
具体实现示例
下面给出一个完整可运行的示例:用中介者协调两个同事,当其中一个触发事件时,中介者通知另一个做出反应。
package main
import "fmt"
type ConcreteMediator struct {
colleagueA *ColleagueA
colleagueB *ColleagueB
}
func (m *ConcreteMediator) Notify(sender Colleague, event string) {
if event == "A_changed" {
m.colleagueB.OnNotify("update_from_A")
} else if event == "B_clicked" {
m.colleagueA.OnNotify("refresh_from_B")
}
}
type ColleagueA struct {
mediator Mediator
}
func (c *ColleagueA) SetMediator(m Mediator) {
c.mediator = m
}
func (c *ColleagueA) OnNotify(event string) {
fmt.Println("ColleagueA received:", event)
}
func (c *ColleagueA) Change() {
fmt.Println("ColleagueA changed, notify mediator")
c.mediator.Notify(c, "A_changed")
}
type ColleagueB struct {
mediator Mediator
}
func (c *ColleagueB) SetMediator(m Mediator) {
c.mediator = m
}
func (c *ColleagueB) OnNotify(event string) {
fmt.Println("ColleagueB received:", event)
}
func (c *ColleagueB) Click() {
fmt.Println("ColleagueB clicked, notify mediator")
c.mediator.Notify(c, "B_clicked")
}
func main() {
med := &ConcreteMediator{}
a := &ColleagueA{}
b := &ColleagueB{}
med.colleagueA = a
med.colleagueB = b
a.SetMediator(med)
b.SetMediator(med)
a.Change()
b.Click()
}
在代码中,ConcreteMediator持有两个具体同事的指针,并在Notify里根据事件类型做分发。同事结构体中的mediator字段类型为接口,因此可替换成任何实现了Mediator的类型。运行后会看到A状态变化触发B更新,B点击触发A刷新,而A和B的源码里完全没有互相引用的代码。
这种写法比在A里直接调用B的方法要好维护得多。如果以后加入同事C,只需让C实现Colleague接口,并在中介者里增加对应分支,原有A和B的代码无需变动。对于Go这种强调组合而非继承的语言,中介者模式能自然融入已有的结构体设计。
与全局事件总线的对比
有些开发者会用全局事件总线来解耦,比如一个包级变量的发布订阅中心。虽然也能让对象互不引用,但事件总线往往缺乏显式的协调逻辑,事件名容易冲突,且流程难以追踪。中介者把交互规则集中在同一结构体内,可读性更强。
| 方案 | 耦合度 | 可追踪性 | 适用场景 |
|---|---|---|---|
| 直接调用 | 高 | 强 | 简单固定逻辑 |
| 全局事件总线 | 低 | 弱 | 跨模块广播 |
| 中介者模式 | 低 | 强 | 多对象协作 |
从上表可以看出,中介者模式在保持低耦合的同时,没有牺牲可追踪性。它特别适合那些对象数量不多、但交互关系复杂的场景,例如UI组件联动、订单状态机中的多方通知等。
实践中的注意事项
中介者本身可能变成“上帝类”,也就是把所有逻辑都堆在里面。为避免这个问题,可以按业务域拆分成多个中介者,而不是用一个超大结构体处理全部交互。另外,如果同事数量极多且交互随机,中介者里的条件分支会膨胀,此时可配合策略模式或查表法来优化分发。
在Golang项目里,建议把中介者接口放在独立的包中,具体同事通过依赖注入获得中介者实例,而不是在init函数里硬编码。这样在测试时能用mock中介者替换真实实现,验证单个同事是否正确发出了通知。合理使用该模式,可以让Go代码的模块边界更清晰,对象关系更可控。