# 20 中介者（行为型）

> 一堆对象两两互相引用，乱成一张 N×N 的网，牵一发动全身。引入一个“中间人”把网状关系收拢成星状，大家只跟中间人打交道。讲清中介者的解耦、它最大的坑(上帝对象)，以及和观察者/外观的区别。

- 作者：David（道雾轩）
- 专栏：经典设计模式浅析（https://daiw.org/manual/design-patterns.md）
- 最后更新：2026-07-12
- 原文：https://daiw.org/manual/design-patterns/mediator
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 20 中介者 · Mediator（行为型）

上一篇责任链把处理者串成“线”。这一篇处理的是更麻烦的拓扑——**一堆对象乱成一张“网”**。

## 一、这是什么：机场塔台

想象一个没有塔台的机场：每架飞机要起降，都得和**其他每一架**飞机直接通话协调——“我要降落，你让让”“你先起飞我等你”。10 架飞机就是几十条两两通话，乱成一锅粥，还极易出事。

现实中当然不是这样。所有飞机只和**塔台**通话，由塔台统一协调谁先谁后。飞机之间**互不直接联系**，全靠塔台这个“中间人”居中调度。10 架飞机，10 条“飞机↔塔台”的线，清清爽爽。

中介者模式就是这座塔台：**用一个中介者对象封装一组对象之间的交互，让这些对象不再互相直接引用，而是都只跟中介者打交道。** 网状的多对多关系，被收拢成星状的一对多。

配套代码是聊天室：`User` 之间不直接持有对方，发消息全通过 `ChatRoom`(中介者)转发，用户彼此解耦。

## 二、解决什么痛：N 个对象两两耦合 = N² 张网

一组对象如果两两直接通信，关系数量是平方级增长的：

```go
// 痛点:每个用户都得持有其他所有用户的引用
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` 的依赖)。

<Callout type="info">
  **中介者隔离的“会变的东西”，是“对象之间那套错综复杂的交互逻辑”。** 它把散落在各个对象里的“我该怎么和其他人打交道”抽出来，集中到中介者一处。每个对象从此只认识中介者一个，变得干净、可独立复用；而“谁和谁怎么互动”的规则，全收敛到中介者里统一维护。网状 → 星状，平方 → 线性。
</Callout>

## 三、怎么实现：大家只认中介者

对象持有中介者引用，要和别人交互时，不直接找对方，而是喊中介者；中介者负责协调转发。

```go title="go/mediator/main.go（核心）"
// 中介者:聊天室,负责协调所有用户之间的消息转发
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)   // ← 我不群发,我告诉中介者,它来
}
```

用户之间零直接引用，加人、改转发规则都只动中介者：

```go
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”则更接近观察者/事件驱动)。

---

中介者把“网”收成“星”。下一篇[备忘录](https://daiw.org/manual/design-patterns/memento)：怎么保存一个对象某时刻的完整状态，以便之后一键恢复(撤销/读档)，还不破坏它的封装——这是所有“Ctrl+Z”和“游戏存档”的底层原理。
