
早几年写业务代码的时候我经常在一个页面里塞十几个回调A组件改状态要通知BB要联动CC又要刷新D最后改一个需求牵一发动全身线上出 bug 都找不到是哪一层传串了。后来系统学了一遍 GoF 的 23 种设计模式做到第 18 个——中介者模式Mediator Pattern的时候才真正想通好架构不是大家都能直接对话而是大家都别说话让一个中间人统一传话。中介者模式属于行为型设计模式核心思想通俗到不行把对象之间的多对多交互改造成对象与一个中介者之间的多对一交互。这个中介者负责协调各个对象的消息流转对象之间不再互相持有引用改而只认中介者一个上级。这篇文章不绕理论直接讲清楚它解决什么问题、代码怎么写、真实场景怎么落、以及那些文档里不会告诉你的坑。写代码的、做架构的、做游戏开发的或者正在赶设计模式期末大作业的同学都能照着思路直接改造自己的项目。1. 中介者模式到底在解决什么问题1.1 先看一段让程序员上火的代码先回想一下没有中介者的日子。假设你在做一个下单页面用户点击提交订单后至少有三个模块要协作订单模块要创建订单、库存模块要扣减库存、消息模块要发送通知。如果让这些模块直接互相调用代码大概会长这样public class Order { private Inventory inventory; private Notifier notifier; public void submit() { boolean ok inventory.deduct(); if (ok) { notifier.send(下单成功); } } } public class Inventory { private Order order; private Notifier notifier; public void restore() { order.markCancelled(); notifier.send(库存已恢复); } }看到没有Order 知道 InventoryInventory 反过来又知道 Order两个类还必须都认识 Notifier。这还只是三个模块要是再加一个优惠券模块、一个物流模块、一个积分模块这些类之间的依赖连线就变成了一个蜘蛛网。谁改了构造方法所有引用它的地方全部编译报错改一个需求嘴上说是五分钟实际上是一下午在修耦合。这种网状交互结构的问题不只是代码丑它让模块无法独立测试、无法替换、无法复用。Order 想单独单测得先 mock 掉 Inventory、Notifier 和半张依赖图。这场景是不是特别熟悉。1.2 从网状耦合到星型解耦中介者模式的思路就是把上面的蜘蛛网捋直所有对象不直接通信而是把消息交给一个中介者由中介者决定转发给哪些对象。结构从多对多变成多对一调用关系从网状变成星型。用个生活类比就好懂了以前没有租房中介的时候你得自己联系房东、联系物业、联系维修工、联系室友每个人你都得记电话、谈条件、处理纠纷。有了中介公司之后你只需要面对一个中介房子有问题找中介合同有疑问找中介房租交给中介。你和其他房源方、房东、维修服务商之间没有直接联系所有信息都在中介这里中转。对应到系统设计上房东维修工室友就是各个业务模块中介公司就是 Mediator 对象。每个业务模块只需要持有中介者的引用不需要知道别的模块的存在。后续增减模块你只需要改中介者内部的转发逻辑不用动其他同事类。这个思想在很多成熟框架里都有影子比如 MVC 里的 Controller 就充当了 View 和 Model 之间的中介者MQ 消息中间件本质上也是让各服务通过消息队列间接通信。2. 模式的核心结构与角色职责2.1 四个角色的分工中介者模式的标准类图里坐着四个角色我在项目里习惯这样理解它们的职责Mediator抽象中介者定义通信接口声明一个通用的消息转发方法比如send(String message, Colleague colleague)。ConcreteMediator具体中介者持有所有同事对象的引用实现具体的协调逻辑核心的转发规则都写在这。Colleague抽象同事类定义业务方法同时持有一个中介者引用需要和其他对象通信时调用中介者的方法。ConcreteColleague具体同事类实现自己的业务逻辑通过中介者与其他同事类交互。这四个角色落在一个典型例子里就是聊天室是具体中介者每个用户是具体同事用户发消息时不是直接发给别人而是发给聊天室聊天室再把消息广播给其他所有用户。我把这种结构对比写出来方便你做项目时直接套用角色核心职责对应业务例子Mediator定义转发消息的抽象接口聊天室接口、调度中心接口ConcreteMediator维护同事引用实现转发规则具体聊天室、具体调度中心Colleague持有中介者引用触发通信用户基类、组件基类ConcreteColleague各自独立的业务逻辑具体用户、具体 UI 组件2.2 关键点同事对象之间的盲盒通信很多初学者上手会犯一个理解误区觉得中介者模式就是把通信逻辑从同事类里抽出来放到中介者类里仅此而已。这只是表面。最核心的点在于具体同事类之间完全不感知对方的存在。什么意思A 同事发消息给中介者时它并不知道这条消息会被转发给谁B 同事可能接收C 同事可能无视这些都取决于中介者内部的逻辑。这让同事类变成了一个盲盒发给中介者就完事不用关心消息的最终去向。这种盲带来的是极高的解耦度新增一个同事类不需要改任何现有同事类只需要在前面的中介者里注册一下、调整转发规则就行。我在实际项目里最直观的感受是重构前 UI 组件互相引用每次加一个新页面组件都战战兢兢生怕遗漏了哪条联动线改成中介者后新组件只管往中介者注册自己中介者统一决定消息怎么分发心态瞬间就不一样了。当然盲也带来了代价中介者内部逻辑会变复杂这个矛盾后面我专门讲怎么缓解。3. 动手实现一个迷你聊天室3.1 用 Java 写一个最小可运行的例子理论讲十遍不如代码跑一遍。我写一个最经典的聊天室案例直接在本地新建一个ChatRoomDemo.java就能跑非常适合设计模式大作业参考或者自己练手。先定义抽象中介者和抽象同事类// 抽象中介者 public abstract class Mediator { // 注册同事到中介者 public abstract void register(Colleague colleague); // 转发消息colleague 是消息发送者 public abstract void send(String message, Colleague colleague); } // 抽象同事类 public abstract class Colleague { protected Mediator mediator; protected String name; public Colleague(String name, Mediator mediator) { this.name name; this.mediator mediator; } // 通过中介者发送消息 public abstract void send(String message); // 接收消息 public abstract void receive(String message); }再写具体中介者——聊天室import java.util.ArrayList; import java.util.List; public class ChatRoom extends Mediator { private ListColleague colleagues new ArrayList(); Override public void register(Colleague colleague) { colleagues.add(colleague); System.out.println(colleague.name 加入了聊天室); } Override public void send(String message, Colleague sender) { // 广播给除了发送者之外的所有成员 for (Colleague colleague : colleagues) { if (colleague ! sender) { colleague.receive(message); } } } }最后写两个具体同事类模拟两个用户public class User extends Colleague { public User(String name, Mediator mediator) { super(name, mediator); } Override public void send(String message) { System.out.println([ name ] 发送消息 message); // 自己不关心消息发给谁交给 mediator 处理 mediator.send(message, this); } Override public void receive(String message) { System.out.println([ name ] 收到消息 message); } public static void main(String[] args) { Mediator chatRoom new ChatRoom(); User alice new User(Alice, chatRoom); User bob new User(Bob, chatRoom); User carol new User(Carol, chatRoom); chatRoom.register(alice); chatRoom.register(bob); chatRoom.register(carol); bob.send(大家好我是 Bob); } }运行后输出效果是Bob 发送消息聊天室广播给除 Bob 外的 Alice 和 Carol两个人都能收到。整个过程里 Alice、Bob、Carol 三个对象之间没有互相持有引用谁发消息都只和自己的中介者打交道。3.2 这段代码里容易被忽略的三个细节这段示例虽然短但有三个细节值得细品。第一send方法里的colleague ! sender判断本质上是中介者的路由规则。不同业务里这个规则完全不同可能是按用户 ID 定向转发可能是按角色转发到特定人群也可能像上面一样广播给所有人。中介者的价值就是把这段路由规则集中在一个地方而不是散落在各个同事类里。第二Colleague构造的时候就把mediator传进去了这说明同事对象依赖的是抽象中介者而非具体聊天室。面向抽象编程以后替换中介者实现比如改成 VIP 过滤版的聊天室不需要改任何同事类代码。第三注册动作mediator.register(user)是显式进行的。中间者必须拿到同事对象的引用才能真正转发这一步漏了就是经典的发消息没反应故障。我在实际项目里见过有人漏掉注册环节排查了半天后来建议把注册逻辑写到工厂方法里从源头保证创建即注册比靠业务调用方自觉要稳得多。4. 真实项目中的应用场景不只是聊天室4.1 机场塔台调度中介者模式最形象的类比聊天室这个例子太常用了以至于很多人以为中介者模式只适合广播消息这类场景。实际上最具代表性的案例是机场塔台调度——几乎每本设计模式书上都会提到这个例子。想一想机场的运行逻辑机场里有客机、货机、地勤车辆、跑道保养车、廊桥操作员如果每架飞机降落前要自己跟地勤车确认有没有冲突、跟廊桥确认对接位置、跟其他飞机确认间距那整个机场的通信链路会爆炸。而现实世界中所有飞机和车辆只跟塔台对话塔台统一安排降落顺序、分配跑道、协调地面车辆。飞机之间不用互相通信地勤车也不用知道哪些飞机会来。对应到代码架构飞机、地勤车就是同事类塔台就是中介者塔台的调度算法就是中介者内部的消息路由规则。这个例子给你一个很重要的启发当一组对象的行为彼此存在先后依赖和资源冲突时中介者模式特别好使。比如任务调度系统里多个任务抢占同一个资源与其让任务之间互相同步协调不如让一个调度中心统一仲裁。4.2 UI 组件联动全选按钮和复选框的经典联动如果你做过前端或桌面端 UI一定处理过全选/全不选这个需求页面里有一个全选按钮、一行表头的选择框、十行列表的选择框。手动写联动逻辑时全选按钮状态变化要通知十个 checkbox十个 checkbox 中任何一个变化又得通知全选按钮和表头还要判断是否全部选中。不用中介者这二十几个组件的状态同步逻辑会散落在每个组件的 change 事件回调里用中介者思路就变成这样class CheckBoxMediator { private CheckBox selectAllBox; private ListCheckBox itemBoxes; void onSelectAllChanged(boolean checked) { for (CheckBox box : itemBoxes) { box.setChecked(checked); } } void onItemChanged() { boolean allChecked itemBoxes.stream().allMatch(CheckBox::isChecked); selectAllBox.setChecked(allChecked); } }每个复选框只感知中介者不感知其他兄弟组件。当你后续要新增一个反选按钮或者一个仅看未选的筛选开关不需要去修改每一个旧组件只需要在中介者里增加分支逻辑扩散到其他组件线上的改动就是零。我实际做过一个电商后台的筛选面板十几个筛选条件彼此联动当时没有中介者改一处联动逻辑要顺着事件链翻三个组件文件。后来重构时把筛选条件全部收敛到一个FilterMediator里新增筛选条件只在这个类里加一行开关判断爽了很多。4.3 微服务编排中的中介者思想把中介者模式放大到系统架构层面你会发现很多系统都在用这个思想只是没叫这个名字。比如微服务拆分之后订单服务、支付服务、库存服务之间不该直接点对点调用否则服务间的通信依赖会变成一团乱麻。常规做法是引入一个编排层或消息中心Kafka、RabbitMQ 那一类订单服务把订单已创建这个事件丢到消息队列支付服务、库存服务自己去订阅关心的消息。服务和消息中心之间依然是多对一的关系消息中心承担了中介者的角色。这个思想被叫做事件驱动架构本质上是中介者模式在分布式场景下的升级版。所以中介者模式对你的价值不止是写一个类这么简单它是建立一套间接通信的思维框架。理解了中介者再去理解事件驱动的发布订阅系统会发现很多概念都是相通的学习成本会直线下降。5. 常见问题与避坑指南5.1 当心上帝中介者中介者膨胀了怎么办中介者模式最遭人诟病的缺点就是具体中介者容易变成上帝对象God Object——所有交互规则都堆在这里几千行代码改一处怕碰坏三处耦合全从同事类转移到了中介者内部等于换了个地方堵。这个问题我的处理原则是中介者里只放路由和协调逻辑不放业务实现。比如聊天室中介者只负责把消息转发给谁具体的消息持久化、敏感词过滤、消息格式化应该委托给专门的服务类中介者只调用这些服务不亲自写几百行过滤规则。再细分的话如果中介者内部的分支实在太多可以考虑按业务维度拆成多个中介者分别协调不同领域的通信避免单一中介者承载过重。判断膨胀有一个很实用的标准如果中介者里的一个if else分支超过三四个或者你写方法时不得不用非常长的注释来解释这步是干什么的基本就该拆了。中介者的方法体应当短小入口进来、分析消息、打到对应处理服务、返回保持每段逻辑寥寥数行这样职责边界才清晰。5.2 中介者和观察者模式别搞混学设计模式的人百分之百会在这两个模式上面犯迷糊中介者模式里同事对象调中介者的方法中介者再调用其他同事的方法观察者模式里发布者通知订阅者。两者都是对象间通信的解耦方案到底啥区别我的记忆办法很简单观察者模式是单向广播发布者不关心谁在听订阅者接收通知后自己处理。典型的消息来了大家各自干各自的事。中介者模式是双向协作中介者不仅负责转发还承担了协调者的角色它决定哪条消息该去哪个同事甚至可以编排多个同事的调用顺序比如先扣库存、再发通知。换句话说观察者模式解决的是通知问题中介者模式解决的是协作问题。中介者内部完全可以用观察者模式来实现组合使用非常常见。在聊天室例子里如果把中介者改成事件总线让每个用户注册订阅事件代码可以写得和中介者模式效果相似但设计意图上谁来控制通信规则的区别还是能看出来的——中介者是主动协调事件总线是被动广播。5.3 消息粒度高导致的方法名过泛该怎么收场中介者的接口方法如果只定义成通用形式比如send(String message)消息里塞的内容太杂会出现一种尴尬局面同事对象把消息发出去中介者打开消息一看发现消息里既没有目标标识也没有消息类型根本不知道该转发给谁。这就是过泛接口的坑。实操中我给中介者接口加一个消息类型参数让路由规则有判断依据public void notify(String eventType, Object data, Colleague sender);同事对象发送时注明事件类型比如mediator.notify(ORDER_CREATED, order, this)中介者里用switch (eventType)决定去协调哪些同事完成后续动作。这样接口看起来还是通用的但消息内容有了结构化语义路径分发的代码就好写很多。还有一种做法是先把消息封装成事件对象把事件类型、来源、负载全部装进去类似public class Event { public String type; // 事件类型 public Colleague source; // 来源同事 public Object payload; // 携带的数据 }这种做法我见过在游戏开发中尤其常用因为游戏对象之间的交互类型非常多通用事件对象能够灵活承载各种触发信号。你看这也是设计模式与游戏完美开发结合起来比较典型的实践游戏里的 AI 系统、音频系统、动画系统之间交互频繁用一个全局事件中介者统一分发各系统都能保持独立演进。5.4 测试和调试的两个独家经验最后分享两个踩过坑换来的实操经验。第一个是关于单测的。中介者模式把通信逻辑集中了测试同事类反而变得很轻松创建 mock 中介者就行同事只跟 mock 交互断言 mock 收到了正确的调用。但测试具体中介者时要格外小心中介者内部持有真实的同事列表测试时传 mock 同事进去要确认注册顺序不会影响路由结果确保中介者逻辑不依赖列表的插入顺序避免测试偶发失败。第二个调试经验场上出现消息莫名其妙没人处理的 bug 时优先检查两个地方先查同事对象有没有成功注册进中介者再查中介者收到消息后走的是哪个分支很多问题都是注册漏掉或者事件类型拼写不一致引起的。给事件类型定义常量或枚举而不是散落字符串字面量这一类问题能少一大半。6. 中介者模式和其他模式的联动玩法6.1 中介者 观察者把对象关系理得清清楚楚前面提过中介者可以和观察者组合这里展开细说一下。我做过的项目里比较舒服的组合方式是中介者做骨干观察者做细节中介者负责确定规则A 模块的事件需要通知哪些模块、先后顺序是什么每个模块内部再用观察者机制监听中介者派发过来的事件。同事类只向中介者注册一次具体怎么处理收到的消息是模块内部的事可以用观察者订阅和解耦内部的多个子组件。举个实际例子我在写一个 FPS 小游戏时角色血量变化会牵动 HUD 血条显示、音效播报、成就系统判断、以及对战逻辑。我没有让角色直接调用这四个系统而是做了一个GameEventMediator角色只发HP_CHANGED事件给中介者中介者按注册关系把事件派发给四个系统四个系统内部各自有观察者监听事件。新增一个死亡回放系统时只需要在中介者里注册它并声明关心哪些事件角色类和已有系统完全不用动。6.2 中介者 工厂模式保证同事注册不漏上面提过的创建即注册思路搭配简单工厂可以形成一套非常顺滑的代码结构。让我用一个更完整的示意来讲这个组合假设一个窗口系统里有主窗口、工具栏、面板、状态栏四个组件每个组件都在构造时要求传入中介者同时中介者必须主动注册该组件。如果每个组件的创建都散落在各处漏注册几乎是必然的。把创建逻辑收归到一个工厂方法里class ComponentFactory { public static UserPanel createUserPanel(Mediator mediator) { UserPanel panel new UserPanel(mediator); mediator.register(panel); return panel; } }这样创建组件并注册中介者就变成一个原子操作调用方拿到的组件天然可用不需要记创建完之后手动注册这个步骤。这个组合模式对小组件特别友好是我多次实践后比较推荐的构造方式。6.3 什么时候真的不该硬套中介者中介者模式虽好但也不是所有场景都适用的。我见到过反模式案例只有两三个对象交互也被强行介入一个中介者类结果业务逻辑被拆得零零碎碎读代码要跳三个文件才能看明白一段完整流程这就是过度设计。我的判断标准很朴素当对象之间的交互是一对一点对点且关系稳定时直接持有引用比引入中介者更简单当交互对象超过三个且相互牵扯、行为存在先后协调时才值得考虑中介者。简单场景直接连线不搞花活复杂场景才需要做信息收口。设计模式的价值不是越多越好而是多到刚好顺手。另外如果你的交互场景变更非常频繁其实可以先考虑用事件总线EventBus或者消息队列这种现成的中介者不要自己造轮子。在单体应用里引入一个成熟的轻量 EventBus 库往往比自己实现一个中介者更省心因为它已经处理好了线程调度、解耦注册、缓存这些边角问题。中介者模式的手工版更适合需要自定义复杂路由编排的场景或者教学阅读、设计模式大作业这类需要展示模式本身结构的场合。7. 最后的实战心得这篇文章写下来是我对中介者模式的完整复盘。从最初一提到多个对象间通信就反射性地加中介者到后来学会先画交互图、数连线再决定该不该用整个过程重新走了一遍。我个人的体会是中介者模式真正的价值不是提供某个最佳实践方案而是逼迫你在设计组件交互时先想清谁跟谁需要说话、消息由谁来决定去向逼你走出事件间直接调用的舒适区把依赖梳理清楚。最后的提示是——动手写一次聊天室或者上手重构一个自己项目里最乱的多组件交互页面比你读十篇设计模式文章都有用。把这段逻辑跑通你就能切实感受到网状变星型之后改动成本骤降带来的爽感。后面我会继续把剩余的几个行为型模式逐一拆解如果有哪篇能帮你在实际开发或期末作业里少走一段弯路这篇码字就值了。