# Mediator（中介者模式）

Mediator（中介者模式）属于行为型模式。

**意图：用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用，从而使其耦合松散，而且可以独立地改变它们之间的交互。**

前端开发中，最常用的 “数据驱动” 其实就最好的诠释了中介者模式。

想一个这样的场景：

1. 按钮点击后，表单提交。按钮需要调用所有表单项获取表单值。
2. 表单关联，当勾选了城市后，才出现满意度 Input 框，此时城市勾选按钮需要引用满意度 Input 框。
3. 甚至会出现循环引用，两个输入框是互斥的，输入了一个，另一个输入框就要 Disable。
4. 当新增加一个表单项时，需要重新建立所有引用关系。

以上过程式编程方式，维护大型项目几乎是不可能的。然而数据驱动可以很好的解决这个问题，所有表单项都依赖数据，并修改数据，这样当 Input 框联动 Check 时，Input 并不需要感知 Checkbox 的存在，他只要关联数据、修改数据就行了，Checkbox 也只要关联数据和修改数据，这样不但逻辑可以独立完成，甚至可以解决循环引用的问题。

**在数据驱动的例子中，数据就是中介。** 所有 UI 之间都不会相互引用，而是通过数据这个中介来协同工作，这样做带来的明显好处是可以处理复杂项目，且易于维护。

## 举例子

如果看不懂上面的意图介绍，没有关系，设计模式需要在日常工作里用起来，结合例子可以加深你的理解，下面我准备了三个例子，让你体会什么场景下会用到这种设计模式。

### 数据驱动

正如开篇说的，数据驱动是中介者非常经典的例子，正是因为引入了 “数据中介者”，才让前端项目的复杂度可以呈几何倍数递增，而代码的逻辑复杂度仅线性递增。因为 UI 是杂乱的且动态的，UI 间依赖会导致关系网非常复杂，且关系网一旦形成，增加一个新元素或修改就变得异常困难。

中介者模式则避开了 UI 间依赖的关系网，通过数据层统一调度，UI 受控响应，可以大大减少逻辑复杂度。

### 解决循环依赖

循环依赖几乎只能利用中介者模式解决：

```typescript
import { b } from './b'
export const a = 'a'
```

```typescript
import { a } from './a'
export const b = 'b'
```

当双方相互引用时，构成循环依赖，不仅对于模块化来说是有问题的，从逻辑上也是讲不通的，因为一定存在递归调用的问题。这是，引入第三方中介者就不仅仅是一种设计模式思维了，而是 a、b 模块中原本就有一些内容是两边公用的，一定需要提出来，而统一提出来的地方就是中介者模式的中介者部分。

### 企业组织架构

一个树状企业组织架构中，每个非叶子结点都是中介者，需要给他的子节点分配任务，并协调他们的工作，这样一来，叶子结点不需要有全局观即可工作，因为他们只需负责 “去做自己的事情”，而不需要关心 “是如何协同的”。

<img width=600 src="https://img.alicdn.com/imgextra/i1/O1CN01BPn79I1mzUt7yWRjB_!!6000000005025-2-tps-582-291.png">

如图所示，环境部不需要关心人事部做了什么，只要专注做好环境事物即可，他们之间的协调由总经理处理，这是一种分工协作的体现。

而只存在于理论中的网状企业管理模型，则是没有中介者的例子，所有节点都是非叶子结点，并相互引用，这样一来每个人既要做自己的工作，又要处理自己与公司里其他几万人的协同，几乎是一件不可能完成的事情，所以从设计模式角度来看，也更倾向于使用树状而不是网状模式管理企业。

## 意图解释

**意图：用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用，从而使其耦合松散，而且可以独立地改变它们之间的交互。**

中介者模式非常好理解，直接看字面意思即可。所谓的对象交互，指的是对象之间是如何协同的，中介者做的是处理对象间协同的工作，而不是 “替每个对象干活”。

最后一句 “可以独立地改变他们之间的交互”，指的是对象之间协同方式不是一成不变的，比如一个输入框组件，只要实现自己的输入功能就行了，而不需要关心是如何与外界交互的。外界可以通过将其嵌入到表单中，成为表单项的一部分，也可以将其包裹一层符号后缀，成为一个专门输入金额的金额输入框。

## 结构图

<img width=600 src="https://img.alicdn.com/imgextra/i4/O1CN01FLHDqJ1c1MjH3fM4k_!!6000000003540-2-tps-1602-440.png">

- Mediator：中介者接口，定义一些通信 API。
- ConcreteMediator：具体的中介者，继承 Mediator，协调各个对象。
- Colleague：同事类，比如之前提到的输入框、文本框，每个同事之间只要知道中介者即可，他们之间不需要知道对方的存在。

## 代码例子

下面例子使用 typescript 编写。

```typescript
const memberA = new Member('美术')
const memberB = new Member('程序')

const picture = memberA.draw() // 美术画出图
const product = memberB.code(picture) // 程序按照美术画的图做产品
```

这个例子中，完成了程序与美术的协同，他们各自不需要知道对方的存在。如果后续又引入了产品、测试工种，他们之间不需要做复杂的关联，只需要在中介者增加对应协同逻辑即可。

## 弊端

中介者模式虽然好，但过度使用可能使中介者逻辑非常复杂。

我们常说管理者直接管理人数最好不要超过二十人，原因是协调本身也非常耗费精力，一个中介者节点如果管理的对象过多，可能会导致中介者本身难以维护，甚至出现 BUG。

另外则是不要过度解耦，当两个对象本身可以构成依赖关系时，使用中介者模式使其强行解耦，带来的只会是更重的理解负担。

## 总结

当一个系统对象很多，且之间关联关系很复杂，交叉引用容易产生混乱时，就可能适用中介者模式。

中介者模式也符合迪米特法则，做到了每个对象了解最少的内容，这样做对于大型程序来说是非常有益的。

> 讨论地址是：[精读《设计模式 - Mediator 中介者模式》· Issue #299 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/299)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
