编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 面向对象设计

  • 常见设计原则

  • 巧学设计模式

    • README
    • 单例模式设计思想
    • 工厂模式设计思想
    • 建造者模式设计思想
    • 原型模式设计思想
    • 静态代理设计模式
    • 动态代理设计模式
    • 适配器模式设计思想
    • 装饰者模式设计思想
    • 外观模式设计思想
    • 桥接模式设计思想
    • 组合模式设计思想
    • 享元模式设计思想
    • 观察者模式设计思想
    • 策略者模式设计思想
    • 模版模式设计思想
    • 迭代器模式设计思想
    • 职责链模式设计思想
    • 命令模式设计思想
    • 状态模式设计思想
    • 备忘录模式设计思想
    • 中介者模式设计思想
      • 01.案例引入:收银页组件网状失控
        • 1.1 痛点现场
        • 1.2 直觉实现复现
        • 1.3 问题根源拆解
        • 1.4 引出本篇主角
      • 02.三次失败探索
        • 2.1 尝试方案 A:事件总线广播
        • 2.2 尝试方案 B:观察者模式一对多
        • 2.3 尝试方案 C:把逻辑下沉到父类
        • 2.4 终于引出中介者模式
      • 03.中介者模式基础介绍
        • 3.1 从失败中提炼的需求
        • 3.2 中介者模式的标准骨架
        • 3.3 典型使用场景
      • 04.三种实现对比
        • 4.1 实现核心要点
        • 4.2 实现 A:集中式单中介
        • 4.3 实现 B:多中介分层编排
        • 4.4 实现 C:中介 + 命令联动
        • 4.5 三种实现速查表
      • 05.用前用后效果对比
        • 5.1 代码维度对比
        • 5.2 协作维度对比
        • 5.3 核心收益
      • 06.反面踩坑实录
        • 6.1 踩坑 A:中介膨胀成上帝类
        • 6.2 踩坑 B:中介成为单点性能瓶颈
        • 6.3 踩坑 C:组件绕过中介直连
        • 6.4 替代方案汇总
      • 07.决策树与选型
        • 7.1 该不该用中介者模式
        • 7.2 选哪种实现方式
        • 7.3 选型清单速查
      • 08.总结与延伸
        • 8.1 设计思想沉淀
        • 8.2 模式联动边界
        • 8.3 思考题与延伸
    • 访问者模式设计思想
    • 解释器模式设计思想
    • 23种设计模式概括
    • 技术写作模板
  • 系统架构设计

  • 编程
  • 巧学设计模式
杨充
2025-09-16
目录

中介者模式设计思想

# 21.中介者模式设计思想

📚 本篇按照「事故复盘 → 失败探索 → 模式登场 → 实现对比 → 效果对比 → 反面踩坑 → 选型决策」的节奏展开,建议按顺序阅读。

# 目录介绍

  • 01.案例引入:收银页组件网状失控
    • 1.1 痛点现场
    • 1.2 直觉实现复现
    • 1.3 问题根源拆解
    • 1.4 引出本篇主角
  • 02.三次失败探索
    • 2.1 尝试方案A:事件总线广播
    • 2.2 尝试方案B:观察者模式一对多
    • 2.3 尝试方案C:把逻辑下沉到父类
    • 2.4 终于引出中介者模式
  • 03.中介者模式基础介绍
    • 3.1 从失败中提炼需求
    • 3.2 中介者模式的标准骨架
    • 3.3 典型使用场景
  • 04.三种实现对比
    • 4.1 实现核心要点
    • 4.2 实现A:集中式单中介
    • 4.3 实现B:多中介分层编排
    • 4.4 实现C:中介 + 命令联动
    • 4.5 三种实现速查表
  • 05.用前用后效果对比
    • 5.1 代码维度对比
    • 5.2 协作维度对比
    • 5.3 核心收益
  • 06.反面踩坑实录
    • 6.1 踩坑A:中介膨胀成上帝类
    • 6.2 踩坑B:中介成为单点性能瓶颈
    • 6.3 踩坑C:组件绕过中介直连
    • 6.4 替代方案汇总
  • 07.决策树与选型
    • 7.1 该不该用中介者模式
    • 7.2 选哪种实现方式
    • 7.3 选型清单速查
  • 08.总结与延伸
    • 8.1 设计思想沉淀
    • 8.2 模式联动边界
    • 8.3 思考题与延伸

# 01.案例引入:收银页组件网状失控

本篇主线:对象之间多对多直连,变成网状泥潭

# 1.1 痛点现场

电商收银页上有 6 个组件:商品列表、优惠券、积分抵扣、配送方式、总价区、下单按钮。双十一大促前夕,运营要求新增"发票选择"组件——改动量评估让前端组长倒吸一口凉气:加 1 个组件,要改其他 6 个组件各 5~8 行联动逻辑。

更致命的是,两个月前的一次线上事故:优惠券和积分互斥的规则同时写在两个组件里——选券时积分面板把上限降为 50%,但积分面板自己又按全价算了一遍,两个逻辑互相覆盖,导致 3 小时内有 1200 单结算金额错误,损失 18 万。

翻出代码——每个组件直接持有其他组件的引用:

// 商品列表改数量
goodsList.onQtyChange = () => {
    totalPanel.refresh();          // 通知总价
    coupon.validate();              // 通知优惠券
    points.recalc();                // 通知积分
    submitBtn.updateEnabled();      // 通知按钮
    shipping.recalcFee();           // 通知运费
};
// 优惠券选用 → 也要通知 5 个兄弟
coupon.onPicked = () => {
    totalPanel.refresh();
    points.recalc();
    submitBtn.updateEnabled();
};
// 6 个组件 × 5 个伙伴 = 30 处互相调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 1.2 直觉实现复现

flowchart LR
    G[商品列表] <--> C[优惠券]
    G <--> P[积分]
    G <--> S[配送]
    G <--> T[总价]
    G <--> B[下单按钮]
    C <--> P
    C <--> T
    C <--> B
    P <--> T
    P <--> B
    S <--> T
    S <--> B
    style G fill:#fee
    style C fill:#fee
    style P fill:#fee
    style S fill:#fee
    style T fill:#fee
    style B fill:#fee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

每个组件都持有其他组件的引用——6 个组件之间 15 条双向线。任何组件都无法单独拎出来复用——它 import 了所有兄弟。

💭 反思:为什么加一个"发票选择"组件需要改 6 个文件?核心问题不是"功能复杂"——而是 协作逻辑散落在各个组件的回调里,每个组件都知道"我应该通知谁",但没有一个地方知道"全局的协作规则是什么"。

# 1.3 问题根源拆解

隐患 现象 业务影响
网状耦合 6 个组件 15 条线,一动牵全身 新人不敢改,改一处要测全部
规则散落 "券和积分互斥"写了两遍 两遍逻辑不一致 → 金额算错
不可复用 商品面板 import 了优惠券 换个页面必须全套搬
新增=灾难 加发票组件改 6 个文件 迭代速度指数衰减
单测困难 测商品要 mock 5 个兄弟 单测成本 > 开发成本

核心矛盾:业务上"组件协作应该是集中编排的",但代码层面每个组件各自管理自己的协作关系——散落各处、规则冲突、新增爆炸。

# 1.4 引出本篇主角

中介者模式的核心思想:让所有组件不再直连,而是把状态变化统一汇报给一个"主持人"(Mediator)。主持人知道全部规则,负责协调谁该做什么。组件之间互相不认识,只认识主持人。

class CheckoutMediator {
    onEvent(source, type) {
        switch (source + ':' + type) {
            case 'goods:qtyChange':
            case 'coupon:picked':
            case 'points:slide':
            case 'shipping:change':
                this.totalPanel.refresh();
                this.points.recalc();
                this.submitBtn.updateEnabled();
                break;
        }
    }
}
// 组件只认识 mediator
class GoodsList {
    constructor(mediator) { this.m = mediator; }
    onQtyChange() { this.m.onEvent(this, 'qtyChange'); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
flowchart LR
    G[商品列表] --> M[CheckoutMediator]
    C[优惠券] --> M
    P[积分] --> M
    S[配送] --> M
    T[总价] --> M
    B[下单按钮] --> M
    M --> G
    M --> C
    M --> P
    M --> S
    M --> T
    M --> B
    style M fill:#e6f3ff
1
2
3
4
5
6
7
8
9
10
11
12
13
14

从网状协作变成星型协作:新增"发票"组件只改中介一行,其他 6 个零改动。

先别急着看实现——下一节我们看看新人通常会先尝试哪些方案。


# 02.三次失败探索

中介者模式不是凭空发明的——它是前人走过 3 条死路之后才提炼出来的。

# 2.1 尝试方案 A:事件总线广播

// 方案A:EventBus 全局广播
class GoodsList {
    onQtyChange() { EventBus.emit('qty:changed', this.data); }
}
class TotalPanel {
    constructor() { EventBus.on('qty:changed', () => this.refresh()); }
    // 也要监听 coupon:picked / points:slide / shipping:change
    // → 每个组件注册 5 个事件,6 个组件 = 30 个注册
}
1
2
3
4
5
6
7
8
9

🧪 验证:

// 看似解耦了——但"谁发出了什么事件、谁在监听"散落在 6 个文件里
// 想回答"qty:changed 发出后发生了什么"——需要全局搜索
// 新增发票组件 → 需要改哪些监听?不知道——只能搜代码
1
2
3

❌ 失败原因:EventBus 解耦了"调用关系",但没解耦"协作规则"。谁广播谁根本没有一个地方能看清全貌——调试靠全局搜索。

💡 反思:我们需要的不只是"组件不互调"——是协作规则集中可视化。

# 2.2 尝试方案 B:观察者模式一对多

// 方案B:每个组件是 Subject + Observer
class GoodsList extends Subject {
    onQtyChange() { this.notify({ type: 'qtyChange', data: this }); }
}
// 总价区同时是多个 Subject 的 Observer
class TotalPanel extends Observer {
    update(msg) {
        if (msg.type === 'qtyChange' || msg.type === 'couponSelect') this.refresh();
    }
}
1
2
3
4
5
6
7
8
9
10

🧪 验证:

// 观察者解决了一对多——但这里的问题是"多对多"
// 每个组件既是 Subject 又是 Observer——角色混乱
// "选了券之后积分应该怎么变"仍然散落在积分面板的 update() 里
// → 两个 Observer 之间的联动规则无处安放
1
2
3
4

❌ 失败原因:观察者是一对多,但收银页是多对多。每个组件既是观察者又是被观察者——网状结构本质上没有改变,只是换了一种写法。

💡 反思:多对多需要有一个独立的"主持人"来编排——不是组件之间互相观察,是大家都向主持人汇报。

# 2.3 尝试方案 C:把逻辑下沉到父类

// 方案C:所有组件继承 BasePanel,父类里写死联动
class BasePanel {
    onChanged(source, data) {
        // ❌ 父类里写死:商品→总价/优惠券/积分/按钮/配送
        // 但"发票"组件来了——父类改不动,因为它是基础类,改了影响所有子类
    }
}
1
2
3
4
5
6
7

🧪 验证:

// 父类只能管通用逻辑——"任何组件变化都刷新总价"
// 但"选了券A后积分降到50%还是全价"这种**特异性**规则无处可放
// 父类膨胀 = 所有子类被拖累
1
2
3

❌ 失败原因:父类适合"通用逻辑",不适合"特异规则"。收银页的复杂之处恰恰是特异性规则多——券和积分互斥、满减和运费联动——这些不能写进父类。

💡 反思:必须有一个专门负责"谁触发什么、谁怎么联动"的独立对象——它知道所有组件的引用和所有业务规则。

# 2.4 终于引出中介者模式

三次失败之后,需求清单收敛了:

必须满足 来自哪一次失败
① 协作规则集中可见,一处看清全局 2.1 EventBus 散落
② 支持多对多协调,而非一对多广播 2.2 观察者局限
③ 特异性规则有专属位置,不污染父类 2.3 父类膨胀
④ 新增组件不影响已有组件 1.2 真实事故

中介者模式的标准答案:

// ① Mediator 持有所有组件引用——协作规则集中在 onEvent() 里
class CheckoutMediator implements Mediator {
    private ItemPanel items; private CouponPanel coupon; // ... ② 知道所有组件
    public void onEvent(Component source, String type) {
        switch (type) {                                   // ③ 特异性规则
            case "qtyChange" -> { total.refresh(); coupon.recheck(items.amount()); }
            case "couponSelect" -> { points.recheck(items.amount()); total.refresh(); }
        }
    }
}
// ④ 组件只认 Mediator——新增组件零侵入
class ItemPanel {
    private Mediator m;
    public void onQtyChange() { m.onEvent(this, "qtyChange"); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

短短几行,协作规则集中一处、组件彼此不认识、新增零侵入。这就是中介者模式的灵魂。


# 03.中介者模式基础介绍

# 3.1 从失败中提炼的需求

回顾 02 节的三次失败和 01 节的事故,中介者模式的设计约束:

约束 来自 代码体现
① 协作规则集中编排 2.1 EventBus / 01 事故 Mediator 持有所有组件引用
② 组件只认 Mediator 2.2 观察者 / 2.3 父类 m.onEvent(this, type) 单点汇报
③ 规则独立可维护 2.3 父类膨胀 特异性规则写在 ConcreteMediator 的 switch/策略表
④ 新增组件零侵入 01 事故 加组件只改 Mediator,其他组件不动

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

# 3.2 中介者模式的标准骨架

// Mediator:中介接口
interface Mediator {
    void onEvent(Component sender, String event, Object payload);
}

// Component:每个组件持有 Mediator,事件向它汇报
class Component {
    protected Mediator mediator;
    public void setMediator(Mediator m) { this.mediator = m; }
}

// ConcreteMediator:持有所有组件 + 集中编排逻辑
class ConcreteMediator implements Mediator {
    private CompA a; private CompB b; private CompC c;   // ① 持有所有组件

    public void onEvent(Component sender, String event, Object payload) {
        switch (sender.getClass().getSimpleName() + ":" + event) {
            case "CompA:change" -> {                       // ② 协作规则集中
                b.refresh(a.getData());                    // ③ 特异性规则
                c.recalc();
            }
            case "CompB:select" -> { c.recalc(); a.highlight(); }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
classDiagram
    class Mediator {
        <<interface>>
        +onEvent(sender, event, payload)
    }
    class ConcreteMediator {
        -compA
        -compB
        -onEvent(sender, event)
    }
    class Component {
        -mediator
        +setMediator(Mediator)
    }
    Mediator <|.. ConcreteMediator
    Component --> Mediator
    ConcreteMediator --> Component
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

三句话记住:Mediator 知所有组件 → Component 只识 Mediator → 协作规则全在 ConcreteMediator。 差异全在中介怎么拆——单中介 vs 多中介 vs 命令联动——这就是下一节三种实现的分岔。

# 3.3 典型使用场景

场景 中介者 协调了什么
复杂表单/收银页 CheckoutMediator 6 组件互联动集中编排
MVC Controller Controller 模型和视图的双向协调
聊天室服务器 Room Server 多客户端消息转发
前端 Redux Store Store + Reducer 组件不互调,所有 action 走 store
微服务编排 Orchestrator 多个微服务间的流程编排
空管塔台 ATC 多飞机起降调度

# 04.三种实现对比

# 4.1 实现核心要点

三种写法本质上是在 中介粒度 / 规则复杂度 / 维护成本 上的不同取舍。实现中介者只需两行骨架:

// 组件只发事件
mediator.onEvent(this, "eventType");  // ① 汇报
// 中介集中编排
switch (event) { case "x" -> c.refresh(); }  // ② 协调
1
2
3
4

差异全在"一个中介还是多个中介、要不要联动命令"。下面按演进顺序逐一展开。

# 4.2 实现 A:集中式单中介

设计权衡:用"中介类可能膨胀"换"所有规则一处看清"

// 实现A:所有协作规则集中在一个 Mediator
public class CheckoutMediator implements Mediator {
    private ItemPanel items; private CouponPanel coupon;
    private PointsPanel points; private TotalPanel total;
    private ShippingPanel shipping; private SubmitButton btn;

    public void onEvent(Component sender, String event, Object payload) {
        switch (event) {
            case "qtyChange" -> {
                coupon.recheck(items.totalAmount());
                points.recalc(items.totalAmount());
                total.refresh(items.totalAmount(), coupon.discount(), points.deduct());
                btn.setEnabled(items.notEmpty() && total.amount() > 0);
            }
            case "couponSelect" -> {
                points.recalc(items.totalAmount() - coupon.discount());  // 券积分互斥
                total.refresh(items.totalAmount(), coupon.discount(), points.deduct());
                btn.setEnabled(total.amount() > 0);
            }
            case "shippingChange" -> {
                total.refresh(items.totalAmount(), coupon.discount(), points.deduct(), shipping.fee());
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

新增"发票组件"——只改 Mediator:

// 加一个 case ——其他 6 个组件零改动
case "invoiceSelect" -> total.refresh(..., invoice.tax());
1
2

优点:规则一处看清,新增组件最小改动。缺点:所有逻辑集中后 Mediator 容易膨胀。适用:组件 ≤ 10 个、Mediator 代码 ≤ 300 行。

# 4.3 实现 B:多中介分层编排

设计权衡:用"跨中介协调成本"换"每个中介小而精"

// 实现B:按子域拆分多个 Mediator
public class CheckoutOrchestrator {       // 顶层编排器
    private PricingMediator pricing;       // 价格子域
    private ShippingMediator shipping;     // 配送子域
    private SubmitMediator submit;         // 提交子域

    public void onEvent(Component sender, String event) {
        pricing.onEvent(sender, event);    // 委托子中介
        shipping.onEvent(sender, event);
        submit.onEvent(sender, event);
    }
}

public class PricingMediator {            // 只管价格相关
    private TotalPanel total; private CouponPanel coupon; private PointsPanel points;
    public void onEvent(Component sender, String event) {
        switch (event) {
            case "qtyChange": case "couponSelect": case "pointsSlide":
                total.refresh(items.amount(), coupon.discount(), points.deduct());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

优点:每个中介职责单一,不会膨胀。缺点:子中介间需要协调时需要顶层编排器转发。适用:组件 > 10 个、业务域清晰可拆分。

# 4.4 实现 C:中介 + 命令联动

设计权衡:用"多写命令类"换"可审计可撤销的编排"

// 实现C:每次编排操作封装为命令
interface MediatorCmd { void execute(); }

public class QtyChangeCmd implements MediatorCmd {
    private CouponPanel coupon; private PointsPanel points;
    private TotalPanel total; private SubmitButton btn;

    public void execute() {
        coupon.recheck(items.amount());
        points.recalc(items.amount());
        total.refresh(items.amount(), coupon.discount(), points.deduct());
        btn.setEnabled(canSubmit());
    }
}

public class CheckoutMediator {
    private Map<String, MediatorCmd> cmds = new HashMap<>();
    public void register(String event, MediatorCmd cmd) { cmds.put(event, cmd); }

    public void onEvent(Component sender, String event) {
        MediatorCmd cmd = cmds.get(event);
        if (cmd != null) cmd.execute();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

优点:每个操作独立封装,配合备忘录可撤销、可审计。缺点:命令类增多。适用:金融结算、需要操作审计和回滚的编排场景。

# 4.5 三种实现速查表

实现方式 规则集中 防止膨胀 审计撤销 适用场景 推荐度
实现A:集中单中介 ✅ ❌ ❌ 组件≤10 ⭐⭐⭐⭐
实现B:多中介分层 ✅ ✅ ❌ 组件>10/域清晰 ⭐⭐⭐⭐⭐
实现C:中介+命令 ✅ ✅ ✅ 金融结算 ⭐⭐⭐⭐⭐

📌 一句话决策:≤10 组件→实现A,>10 组件或域清晰→实现B,需要审计回滚→实现C。


# 05.用前用后效果对比

用 1.1 节收银页做基准。

# 5.1 代码维度对比

// ❌ 用前:组件直连
class GoodsList {
    private CouponPanel coupon; private TotalPanel total;
    private PointsPanel points; private SubmitButton btn; // 认识 5 个兄弟
    onQtyChange() { total.refresh(); coupon.validate(); points.recalc(); btn.enable(); }
}

// ✅ 用后:组件只识 mediator
class GoodsList {
    private Mediator m;
    onQtyChange() { m.onEvent(this, "qtyChange"); }  // 只一行
}
1
2
3
4
5
6
7
8
9
10
11
12

# 5.2 协作维度对比

维度 ❌ 组件直连 ✅ 中介者模式
组件间引用数 6 组件 × 5 引用 = 30 处 0(互不认识)
新增组件改动 改 6 个文件 改 1 个 Mediator
规则可见性 散落 6 个文件 集中 switch/策略表
规则冲突风险 高(券积分写两份) 低(一处定义)
组件复用 不可(import 了全部兄弟) 可(只依赖 Mediator 接口)
单测成本 mock 5 个兄弟 mock 1 个 Mediator
事故(积分券互斥) 1200 单算错,损失 18 万 0

# 5.3 核心收益

中介者模式的本质:把"多对多网状协作"集中到一个主持人对象中编排,组件只认识主持人。这正是为什么前端 Redux 所有 action 走 store、为什么聊天室服务器集中转发、为什么空管塔台指挥所有飞机——任何"组件间多对多联动"的场景,让协作规则集中、组件互不认识,才能让"耦合可控 / 规则可见 / 新增零侵入 / 组件可复用"同时成立。


# 06.反面踩坑实录

中介者模式不是银弹——以下 3 个坑几乎每个团队都踩过。

# 6.1 踩坑 A:中介膨胀成上帝类

// ❌ 所有业务逻辑都往 Mediator 里塞
class CheckoutMediator {
    public void onEvent(...) {
        // 结算逻辑、地址校验、库存预占、埋点上报、风控调用……全在一个类
        if (event == "submit") {
            addressValidator.check(...); riskClient.audit(...);
            inventoryService.preLock(...); couponClient.redeem(...);
            eventTracker.send(...); logService.record(...);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

💣 事故:某 CMS 系统中介 800+ 行,任何改动都要改它——回归测试 3 天。最后没人敢动,业务方怨声载道。

✅ 正解:一行原则——Mediator 超过 300 行必须拆分。按子域拆多中介(定价/配送/提交),中介只做编排不写业务逻辑。

# 6.2 踩坑 B:中介成为单点性能瓶颈

// ❌ 中介里做了同步 RPC 调用
class CheckoutMediator {
    public void onEvent(Component sender, String event) {
        if ("qtyChange".equals(event)) {
            RiskResult risk = riskClient.check(userId);  // RPC 200ms
            total.refresh(price * risk.discount);
        }
    }
}
1
2
3
4
5
6
7
8
9

💣 事故:某电商收银页每次点加减按钮都要 RPC 查风控——200ms 延迟,用户狂点后页面冻住。投诉量双十一当天翻了 40 倍。

✅ 正解:中介只做同步编排——实时联动逻辑放本地。RPC/DB 查询用异步 + loading 状态,或在前端做本地缓存。

# 6.3 踩坑 C:组件绕过中介直连

// ❌ 开发者觉得"就调一下"——走了捷径
class GoodsList {
    private Mediator m;
    private TotalPanel total;  // ❌ 又保留了直接引用!
    onQtyChange() {
        total.refresh();        // 绕过中介直接调——规则不经过编排
        m.onEvent(this, "qtyChange");
    }
}
1
2
3
4
5
6
7
8
9

💣 事故:某收银系统 3 个组件保留了"偷懒直连"——选券时绕过中介直接刷新总价,中介里更新了积分限制但总价先刷新了——积分限制不生效,重复算错金额。

✅ 正解:组件注入时只注入 Mediator 接口——不暴露任何兄弟引用;Code Review 卡控"Component 构造函数不允许接收其他 Component"。

# 6.4 替代方案汇总

你的需求 推荐方案
组件 ≤ 3 且联动极少 ✅ 直接互调,别引入中介
单向数据流(如 Redux) ✅ Store + Reducer,天然中介
纯事件广播无协作 ✅ EventBus / 观察者
复杂流程编排+审批 ✅ 工作流引擎 Camunda
已用命令模式+需审计 ✅ 实现C:中介+命令联动

# 07.决策树与选型

# 7.1 该不该用中介者模式

flowchart TD
    Start([我的对象之间有复杂协作吗]) --> Q1{组件数 ≥ 5 且<br/>两两互调?}
    Q1 -->|是| Yes1[✅ 中介者模式]
    Q1 -->|否| Q2{新增组件需要改<br/>其他 N 个文件?}
    Q2 -->|是| Yes2[✅ 中介者模式]
    Q2 -->|否| Q3{协作规则散落<br/>在多个文件里冲突?}
    Q3 -->|是| Yes3[✅ 中介者模式]
    Q3 -->|否| No1[❌ 直接互调即可]

    style Yes1 fill:#dfd
    style Yes2 fill:#dfd
    style Yes3 fill:#dfd
    style No1 fill:#fee
1
2
3
4
5
6
7
8
9
10
11
12
13

# 7.2 选哪种实现方式

flowchart TD
    Start([选择中介者实现]) --> Q1{组件数 > 10 或<br/>业务域清晰可拆分?}
    Q1 -->|是| OptB[实现B:多中介分层编排]
    Q1 -->|否| Q2{需要操作审计<br/>或命令撤销?}
    Q2 -->|是| OptC[实现C:中介 + 命令联动]
    Q2 -->|否| OptA[实现A:集中式单中介]

    style OptB fill:#fff4e6
    style OptC fill:#e6ffe6
    style OptA fill:#e6f3ff
1
2
3
4
5
6
7
8
9
10

# 7.3 选型清单速查

场景 该用吗 推荐方式
收银页 6 组件互联动 ✅ 该用 实现A:集中单中介
复杂 ERP 30 组件 ✅ 该用 实现B:多中介分层
金融结算需审计回滚 ✅ 该用 实现C:中介+命令
聊天室多客户端 ✅ 该用 实现A
2 个组件简单联动 ❌ 别用 直接互调
前端 Redux 已用 ❌ 别用 Store 天然中介

# 08.总结与延伸

# 8.1 设计思想沉淀

阶段 学到了什么
01 事故 痛点是模式诞生的土壤——1200 单算错因协作规则散落各处互相冲突
02 三次失败 EventBus/观察者/父类下沉都不够——多对多必须有一个独立的编排者
03 模式基础 三大角色:Mediator 知全部、Component 只识 Mediator、规则在 ConcreteMediator
04 三种实现 单中介/多中介/命令联动本质是"集中度/膨胀风险/审计能力"的权衡
05 效果对比 30 处互调→0;新增改 6 文件→改 1 行
06 反面踩坑 上帝类膨胀、单点瓶颈、绕过直连
07 决策树 组件 ≤ 3 且联动极少——直接互调更简单

🔑 一句话核心:

中介者 = 网状协作星型化的协调者。组件只识主持人,规则集中一处编排。

# 8.2 模式联动边界

flowchart LR
    中介 -.编排操作封装成命令.-> 命令
    中介 -.内部用观察者分发.-> 观察者
    中介 -.持有状态机按状态分发.-> 状态
    外观 -.单向简化入口.-> 子系统
1
2
3
4
5
模式 关系 一句话区别
外观 易混 外观:单向简化入口(外部→子系统);中介:双向协调(对等同事之间)
观察者 内部配合 观察者是一对多广播;中介内部可用观察者实现事件分发
命令 联动 中介里的编排可封装为命令——可审计可撤销
状态 联动 中介常持有页面状态机,按状态分发事件

什么时候不该用中介者:

  • 组件 ≤ 3 且联动极少——直接互调
  • 已是单向数据流架构(Redux/Vuex)——Store 天然是中介
  • 纯事件广播无协作编排——EventBus 更轻

# 8.3 思考题与延伸

💭 三道思考题:

  1. PM 提出新需求:用户每次操作要发埋点——埋点该写在中介里还是各组件里?为什么?(提示:开闭原则——加埋点不应改中介,AOP 思路更优)

  2. 如果 CheckoutMediator 超过 500 行——拆分原则是什么?按事件类型拆还是按业务子域拆?

  3. 收银页的中介模式在 React/Vue 框架里怎么落地——组件通信用 props、context 还是全局 store?它们本质是不是中介?

📚 延伸阅读:

  • 前端 Redux 源码:Store.dispatch → Reducer 的中介模式
  • Spring MVC DispatcherServlet:HTTP 请求的中介编排
  • Apache Camel:企业集成中的中介者管道
  • Android ViewModel:Fragment 间通信的中介

上一篇 20.备忘录模式 → 本篇 → 22.访问者模式:经典而冷门的"双分派"——看看它解决了什么独特问题。

上次更新: 2026/06/17, 11:43:57
备忘录模式设计思想
访问者模式设计思想

← 备忘录模式设计思想 访问者模式设计思想→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式