20 中介者(行为型)
一堆对象两两互相引用,乱成一张 N×N 的网,牵一发动全身。引入一个“中间人”把网状关系收拢成星状,大家只跟中间人打交道。讲清中介者的解耦、它最大的坑(上帝对象),以及和观察者/外观的区别。
20 中介者 · Mediator(行为型)
上一篇责任链把处理者串成“线”。这一篇处理的是更麻烦的拓扑——一堆对象乱成一张“网”。
一、这是什么:机场塔台
想象一个没有塔台的机场:每架飞机要起降,都得和其他每一架飞机直接通话协调——“我要降落,你让让”“你先起飞我等你”。10 架飞机就是几十条两两通话,乱成一锅粥,还极易出事。
现实中当然不是这样。所有飞机只和塔台通话,由塔台统一协调谁先谁后。飞机之间互不直接联系,全靠塔台这个“中间人”居中调度。10 架飞机,10 条“飞机↔塔台”的线,清清爽爽。
中介者模式就是这座塔台:用一个中介者对象封装一组对象之间的交互,让这些对象不再互相直接引用,而是都只跟中介者打交道。 网状的多对多关系,被收拢成星状的一对多。
配套代码是聊天室:User 之间不直接持有对方,发消息全通过 ChatRoom(中介者)转发,用户彼此解耦。
二、解决什么痛:N 个对象两两耦合 = N² 张网
一组对象如果两两直接通信,关系数量是平方级增长的:
// 痛点:每个用户都得持有其他所有用户的引用
type User struct {
friends []*User // ← 我要认识所有人才能给他们发消息
}
func (u *User) Send(msg string) {
for _, f := range u.friends {
f.Receive(msg) // 直接调用每一个
}
}3 个用户还好,30 个呢?每加一个用户,要把它塞进其他所有人的名单;改一处交互规则(比如“禁言某人”),得在每个对象里改。对象间的关系变成一张 N×N 的蛛网,牵一发动全身,谁也没法单独复用(一个 User 拖着对所有其他 User 的依赖)。
中介者隔离的“会变的东西”,是“对象之间那套错综复杂的交互逻辑”。 它把散落在各个对象里的“我该怎么和其他人打交道”抽出来,集中到中介者一处。每个对象从此只认识中介者一个,变得干净、可独立复用;而“谁和谁怎么互动”的规则,全收敛到中介者里统一维护。网状 → 星状,平方 → 线性。
三、怎么实现:大家只认中介者
对象持有中介者引用,要和别人交互时,不直接找对方,而是喊中介者;中介者负责协调转发。
// 中介者:聊天室,负责协调所有用户之间的消息转发
type ChatRoom struct{ users []*User }
func (r *ChatRoom) Broadcast(sender *User, message string) {
for _, u := range r.users {
if u != sender {
u.Receive(sender.Name, message) // ← 转发由中介者统一负责
}
}
}
// 同事对象:用户,只持有中介者,不直接引用其他用户
type User struct {
Name string
mediator ChatMediator // ← 只认识"塔台",不认识其他"飞机"
}
func (u *User) Send(message string) {
u.mediator.Broadcast(u, message) // ← 我不群发,我告诉中介者,它来
}用户之间零直接引用,加人、改转发规则都只动中介者:
alice.Send("大家好!") // Alice 只跟 ChatRoom 说,ChatRoom 转发给 Bob、Carol四、有哪些坑
- 头号大坑:中介者膨胀成“上帝对象”。 这是中介者的阿喀琉斯之踵。你把对象间的复杂度从“四散的网”收进了中介者,但如果不加节制,这些复杂度会全部堆积在中介者内部,让它变成一个几千行、无所不知、无所不管的巨无霸。中介者是把复杂度“搬家”,不是“消灭”——搬进去之后还得好好组织,否则你只是把一团乱麻从外面塞进了一个盒子。真复杂时,中介者内部可能还要再拆分。
- 和观察者的区别。 观察者是单向的一对多广播(主题变→通知观察者,观察者一般不“回话”);中介者是双向的多对多协调(同事们互相之间的双向交互都经它中转)。聊天室里 A 发 B 收、B 也能发 A 收,是双向的,所以是中介者而非观察者。
- 和外观的区别。 外观是单向简化——客户端通过外观调用子系统,但不管子系统内部之间怎么协作;中介者管的正是同级对象之间的相互协作。外观对着“外面”(简化调用入口),中介者对着“里面”(协调内部成员)。
五、开源里怎么用
- MVC/MVP 里的 Controller/Presenter:它常常扮演 Model 和 View 之间的中介者——View 的操作交给 Controller,Controller 协调 Model 更新再刷新 View,View 和 Model 不直接对话。
- 事件总线 / Event Bus / 消息总线:组件不互相直接调用,而是把事件发到总线,由总线分发。这是中介者在大型前端(如各种全局 EventBus)和后端(进程内消息总线)里的常见形态。
- UI 对话框协调多个控件:一个复杂表单里,“选了 A 就禁用 B、填了 C 就显示 D”这类联动,若让控件两两直接引用会乱套,通常由对话框本身作中介者统一协调。
- 微服务编排(Orchestration):一个编排服务协调多个下游微服务的调用顺序与依赖,让下游服务彼此解耦——中介者思想在分布式架构层面的放大(与之相对的“编舞 Choreography”则更接近观察者/事件驱动)。
中介者把“网”收成“星”。下一篇备忘录:怎么保存一个对象某时刻的完整状态,以便之后一键恢复(撤销/读档),还不破坏它的封装——这是所有“Ctrl+Z”和“游戏存档”的底层原理。