zhangdizhangdi

中介者模式 📖

如果说发布订阅模式解决的是“消息如何优雅地传递”,那么中介者模式(Mediator Pattern)解决的则是 “如何驯服复杂的网状依赖关系”

定义

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

没有中介者:就像没有红绿灯的十字路口,车与车之间需要互相观察、按喇叭沟通(网状结构的强耦合),极易造成死锁和车祸。
有中介者:就像机场的航空塔台。几十架飞机之间绝对不会直接通过无线电商量谁先降落,它们全都只跟塔台汇报自己的状态,由塔台统筹调度(星型结构的松耦合)。

  • Mediator(中介者):定义各个同事对象通信的接口/逻辑。
  • Colleague(同事类):各个具体的业务组件。它们互相不认识,它们只认识中介者。当自己的状态发生变化时,只需告诉中介者。

🥊 绝佳对比:中介者 vs 发布订阅模式

因为两者都是用来“解耦”的,很多人会把它们搞混。核心区别在于 “谁拥有业务逻辑(大脑)”

维度 发布订阅模式 (Event Bus) 中介者模式 (Mediator)
业务逻辑 调度中心不关心业务。它只负责:谁发了A事件,我就把数据无脑塞给订阅了A事件的人。交互逻辑写在订阅者内部。 中介者包含大量的业务编排逻辑。当A组件发生变化时,中介者内部决定:是去调用B,还是调用C,还是先B后C。
通信内容 传递的是“事件(Event)”和“数据” 传递的是“行为”和“指令”
拓扑结构 松散的单向流 严密的星型结构(中心化)

总结:发布订阅模式是纯粹的通信机制;中介者模式是一种架构设计,用于处理复杂的协同逻辑。

与设计原则的关系

  1. 迪米特法则 (最少知识原则 - Law of Demeter)
    • 这是中介者模式体现最淋漓尽致的原则。组件 A 不需要知道组件 B、C、D 的存在,它只需要知道中介者即可。将原本 $O(N^2)$ 的网状依赖,简化成了 $O(N)$ 的星型依赖。
  2. 单一职责原则 (Single Responsibility Principle)
    • 各个“同事”组件只专注于自己内部的 UI 或数据逻辑。
    • “组件间的协同配合逻辑”被剥离出来,交给了中介者负责。
  3. ⚠️:可能违背“开闭原则”和“单一职责原则”(中介者的通病)
    • 这是中介者模式最大的缺点——随着业务变庞大,中介者对象会变成一个极其臃肿的“上帝对象”(God Object),难以维护。

实现

智能家居

如果不用中介者:当“打开大门”时,门需要直接调用 Light.turnOn(),还要调用 AC.turnOn()。门被死死绑定了灯和空调。
使用中介者:门只需告诉智能中控(Mediator),中控去协调一切。

javascript
// 1. 中介者 (Smart Home Center)
class SmartHomeMediator {
  constructor() {
    this.devices = {};
  }
  
  // 注册设备
  register(device) {
    this.devices[device.name] = device;
    device.setMediator(this); // 给设备绑定中介者
  }

  // 核心逻辑:中介者处理各个设备的协调!
  // 这里的 notify 可以理解为塔台接收到了飞机的信号
  notify(sender, event) {
    if (sender.name === 'Door' && event === 'open') {
      console.log('--- 中控:检测到门打开,开始回家模式 ---');
      this.devices['Light'].turnOn();
      this.devices['AirConditioner'].setTemp(26);
    } 
    else if (sender.name === 'PC' && event === 'play_movie') {
      console.log('--- 中控:检测到电脑播放电影,开始影院模式 ---');
      this.devices['Light'].turnOff();
    }
  }
}

// 2. 同事类基类 (Colleague)
class Device {
  constructor(name) {
    this.name = name;
    this.mediator = null;
  }
  setMediator(mediator) {
    this.mediator = mediator;
  }
}

// 3. 具体设备 (它们彼此完全不知道对方的存在)
class Door extends Device {
  open() {
    console.log(`${this.name} 开启了`);
    this.mediator.notify(this, 'open'); // 通知中介者
  }
}

class Light extends Device {
  turnOn() { console.log(`${this.name} 亮起`); }
  turnOff() { console.log(`${this.name} 熄灭`); }
}

class AirConditioner extends Device {
  setTemp(temp) { console.log(`${this.name} 温度设置为 ${temp}度`); }
}

// ================= 使用 =================
const mediator = new SmartHomeMediator();

const door = new Door('Door');
const light = new Light('Light');
const ac = new AirConditioner('AirConditioner');

// 把设备接入中控
mediator.register(door);
mediator.register(light);
mediator.register(ac);

// 触发动作
door.open(); 
// 输出:
// Door 开启了
// --- 中控:检测到门打开,开始回家模式 ---
// Light 亮起
// AirConditioner 温度设置为 26度

前端应用

在前端开发中,中介者模式经常在不知不觉中被我们大量使用:

1. 复杂的表单联动 (Form Validation / Linkage)

假设有一个电商结账页面,包含:国家下拉框、省份下拉框、运费计算展示、提交按钮。

  • 交互逻辑极复杂:选择国家 → 更新省份 → 更新城市;选了偏远地区,运费要加钱;信息没填完,提交按钮置灰。
  • 中介者应用:我们通常会写一个大的 FormController(或者现在的 React/Vue 中的根组件)。各个小的 Input 框组件只管自己输入,把 onChange 传给父组件。这个父组件(或者 Form 实例)其实就是一个中介者,它统筹所有的联动逻辑。

2. MVC 架构模式 / MVVM 的 ViewModel

  • 在经典的 MVC 模式中,C(Controller,控制器) 本质上就是一个中介者。View 接收到点击,通知 Controller;Controller 去修改 Model;Model 发生变化,又由 Controller 决定去更新哪个 View。View 和 Model 之间被完全解耦了。
  • MVVM(如 Vue)中,ViewModel(Vue 实例) 充当了隐式的中介者。它居中调度数据的绑定和 DOM 的更新。

3. 状态管理库 (Redux / Vuex / Pinia)

很多人把 Redux 当作发布订阅模式,但如果从宏观架构来看,它的 Store 本质上是一个超级中介者

  • 所有 React/Vue 视图组件互相之间不通信(取消了原本乱七八糟的父子、兄弟组件传参)。
  • 组件只向 Store(中介者)发送动作(dispatch(action))。
  • Store 内部有 Reducers 等集中式业务逻辑,计算出新状态,然后通知组件更新渲染。

4. 游戏开发中的场景管理器

前端开发 H5 游戏或 WebGL 时,一个战斗场景中可能有几十个敌人、玩家自身、子弹、障碍物。如果子弹自己去判断有没有打中每一个敌人,逻辑会崩溃。
通常会有一个 SceneManagerGameEngine(中介者),所有物体每一帧把坐标报给管理器,由管理器统一做碰撞检测,然后告诉子弹“你销毁”,告诉敌人“你掉血”。

总结

当你发现前端组件内部有大量的 import ComponentBimport ComponentC,或者有乱七八糟的跨组件互相调用时,你就应该思考:我是不是该抽离一个统一的中介者(如父组件、全局 Store、专门的 Controller 类)来接管它们了? 这就是中介者模式的核心价值所在。