中介者模式名字玄但解决的问题特别接地气:一堆对象彼此互相调用导致依赖网爆炸时,引入一个"中介",让它们只和中介通信。聊天室、表单字段联动、机场塔台、空管系统、消息总线 —— 这些场景都是中介者。这篇文章从一个真实的"表单联动地狱"讲起,看清中介者怎么把 N×N 的依赖压成 N×1。
问题:对象之间互相调,关系成"网"
看一个常见的表单需求:用户类型(个人/企业)选择影响后续字段;选了企业要显示税号字段;勾选"和地址一致"要把发票地址同步成订单地址;金额超过 10 万要显示"风险提示"。朴素实现:
class UserTypeSelect {
public void onChange(String value) {
if (value.equals("company")) {
taxIdField.show();
companyNameField.show();
individualIdField.hide();
} else {
taxIdField.hide();
companyNameField.hide();
individualIdField.show();
}
// 还要影响:发票类型默认值
invoiceTypeField.setDefault(value.equals("company") ? "VAT" : "ORDINARY");
// 还要更新:风险提示显示规则
riskNotice.recalc(value, amountField.getValue());
}
}
class AmountField {
public void onChange(double value) {
if (value > 100000) riskNotice.show();
else riskNotice.hide();
feeField.recalc(value);
// 还要触发:用户类型的相关联动
// 还要影响发票字段...
}
}
每个字段都知道其他字段,改一个字段要触发一堆别的字段。N 个字段 = N×N 的依赖,改一处影响一片。这是"网状耦合"的典型,加新字段要在多处改代码。
中介者的解法
引入一个 FormMediator,字段只通知它"我变了",由 Mediator 决定"还要联动谁":
// 1. 中介者接口
interface FormMediator {
void onFieldChange(String fieldName, Object value);
}
// 2. 字段(同事 Colleague):只和 Mediator 通信
abstract class FormField {
protected final FormMediator mediator;
protected boolean visible = true;
protected Object value;
public FormField(FormMediator m) { this.mediator = m; }
public void setValue(Object v) {
this.value = v;
mediator.onFieldChange(getName(), v); // 只通知 Mediator
}
public void show() { visible = true; render(); }
public void hide() { visible = false; render(); }
abstract String getName();
abstract void render();
}
// 3. 中介者实现:集中所有联动逻辑
class OrderFormMediator implements FormMediator {
UserTypeSelect userType;
InputField taxId;
InputField companyName;
InputField individualId;
SelectField invoiceType;
NumberField amount;
Label riskNotice;
public void onFieldChange(String name, Object value) {
switch (name) {
case "userType":
if ("company".equals(value)) {
taxId.show(); companyName.show(); individualId.hide();
invoiceType.setValue("VAT");
} else {
taxId.hide(); companyName.hide(); individualId.show();
invoiceType.setValue("ORDINARY");
}
recalcRisk();
break;
case "amount":
recalcRisk();
break;
}
}
private void recalcRisk() {
boolean show = amount.getNumber() > 100000 || "company".equals(userType.getValue());
if (show) riskNotice.show(); else riskNotice.hide();
}
}
字段之间的所有联动规则集中到一处。新加联动 = 改 Mediator 一个方法,字段类不动;新加字段 = 给 Mediator 加一个 case,其他字段不受影响。
中介者的标准结构
角色:
- Mediator(中介者):定义对象交互的接口。
- ConcreteMediator(具体中介者):协调各个 Colleague。
- Colleague(同事):每个参与协作的对象,只持有 Mediator 引用,事件通过 Mediator 传播。
关键:Colleague 之间不直接互调,所有交互通过 Mediator。从依赖图看,从 N×N 变成 N×1(每个 Colleague → Mediator)。
实战 1:聊天室
interface ChatRoom {
void register(User user);
void send(String from, String to, String message);
void broadcast(String from, String message);
}
class ConcreteChatRoom implements ChatRoom {
private final Map<String, User> users = new HashMap<>();
public void register(User u) {
users.put(u.name(), u);
u.setChat(this);
}
public void send(String from, String to, String msg) {
User receiver = users.get(to);
if (receiver != null) receiver.receive(from, msg);
}
public void broadcast(String from, String msg) {
for (User u : users.values())
if (!u.name().equals(from)) u.receive(from, msg);
}
}
class User {
private final String name;
private ChatRoom chat;
public User(String name) { this.name = name; }
public String name() { return name; }
public void setChat(ChatRoom c) { this.chat = c; }
public void sendTo(String to, String msg) { chat.send(name, to, msg); }
public void broadcast(String msg) { chat.broadcast(name, msg); }
public void receive(String from, String msg) {
System.out.println(name + " <- " + from + ": " + msg);
}
}
ChatRoom room = new ConcreteChatRoom();
User a = new User("Alice"); User b = new User("Bob"); User c = new User("Charlie");
room.register(a); room.register(b); room.register(c);
a.sendTo("Bob", "hi"); // 通过 room 发,Bob 收到
b.broadcast("good morning everyone"); // 广播
用户之间互不持有引用 —— 即使有 100 个用户,每个用户只依赖一个 ChatRoom 实例。这正是中介者最经典的应用。
实战 2:GUI 框架的事件循环
iOS 的 NotificationCenter、Android 的 EventBus、Vue 的 EventBus(Vue 2)、Node 的 EventEmitter —— 都是中介者的工程实现:
// 发布订阅风格的中介者
class EventBus {
private final Map<String, List<Consumer<Object>>> handlers = new HashMap<>();
public void on(String event, Consumer<Object> h) {
handlers.computeIfAbsent(event, k -> new ArrayList<>()).add(h);
}
public void emit(String event, Object data) {
List<Consumer<Object>> list = handlers.get(event);
if (list != null) for (Consumer<Object> h : list) h.accept(data);
}
}
// 任意组件:不知道彼此存在
class CartWidget {
public CartWidget(EventBus bus) {
bus.on("product.added", p -> addToCart((Product) p));
}
}
class HeaderBadge {
public HeaderBadge(EventBus bus) {
bus.on("cart.changed", count -> updateBadge((int) count));
}
}
class ProductCard {
public ProductCard(EventBus bus, Product p) {
button.onClick(() -> bus.emit("product.added", p));
}
}
组件间完全解耦 —— 加新组件只需要"订阅它感兴趣的事件 + 发布自己的事件",不需要其他组件知道它的存在。这是大型前端应用解耦的常用招数。
实战 3:塔台模型
GoF 原书举的例子 —— 飞机和飞机之间不直接通信,都和塔台(Mediator)通信。塔台决定谁起飞、谁降落、避免碰撞。
真实空管系统(ATC)就是高度可靠的中介者实现。系统价值之大,可以用一个数据观察:全球数千架飞机同时在天上,每天碰撞次数接近 0 —— 这背后是中介者模式在做事。
实战 4:微服务架构里的服务总线
微服务之间如果直接互调(A → B → C → D),依赖网会很糟糕。引入消息总线(Kafka / RabbitMQ / NATS)作为中介者:
// 订单服务发布"订单创建"事件,不知道谁会消费
orderService.create(order);
kafka.publish("order.created", order);
// 库存、积分、推荐、通知服务各自订阅
inventoryService.subscribe("order.created", ...);
loyaltyService.subscribe("order.created", ...);
recommendService.subscribe("order.created", ...);
notificationService.subscribe("order.created", ...);
// 加新订阅者完全不影响订单服务
这是"事件驱动架构(EDA)"的核心 —— Kafka 之类的消息总线就是分布式系统的"超级中介者"。它让每个服务只依赖总线 schema,而不依赖具体的下游服务。
中介者 vs 观察者
两者都用于"对象间通信解耦",区别:
- 观察者:Subject 主动通知所有 Observer,关系是一对多的"广播"。Subject 知道自己有观察者(虽然不知道具体是谁)。
- 中介者:多个对象互相通信,中介者协调。多对多的"协作"。Colleague 互相不知道,只知道 Mediator。
观察者偏重"通知",中介者偏重"协调"。但很多工程实现两者结合 —— EventBus 既是中介者(集中通信)也是观察者(发布订阅)。
中介者 vs 外观
两者都"对外提供统一入口",区别:
- 外观:单向 —— 客户端 → 外观 → 子系统。子系统不知道外观存在。
- 中介者:双向 —— Colleague ↔ Mediator ↔ Colleague。Colleague 主动通过 Mediator 通信。
中介者的代价
1. Mediator 容易变成"上帝类"
所有协作逻辑都堆到 Mediator,它最终变成几千行的复杂类。这是中介者最大的缺点。缓解办法:
- 按业务域拆分多个 Mediator(订单 Mediator、支付 Mediator、库存 Mediator)。
- 把 Mediator 内部的处理逻辑用策略模式或责任链拆开。
- 事件总线代替显式 Mediator,Colleague 直接订阅自己感兴趣的事件,而不是把所有逻辑塞中央。
2. 间接调用导致追踪困难
"A 怎么影响了 D"在中介者里要查 Mediator 的所有处理函数,不像直接调用一眼看到。事件总线尤其如此 —— 发出一个事件,你不知道谁在响应。补救:有完整的事件订阅清单 / 文档,加 Tracing(发事件时打 trace id,接收方也记录)。
3. 测试需要 mock Mediator
测试单个 Colleague 时,要 mock Mediator 验证"它发出了正确的通信"。这增加测试样板。但相比"测试一个紧耦合系统",这种 mock 仍然好得多。
什么时候用中介者
触发信号:
- 对象间互相调用形成网(N×N 边)。
- 这些对象的协作逻辑可以集中描述。
- 对象本身的职责清晰(渲染、数据持有),但"它们之间怎么协作"是另一回事。
函数式风格的中介者
// JS 风格,无类,纯函数
function createMediator() {
const handlers = {};
return {
on(event, handler) { (handlers[event] ??= []).push(handler); },
emit(event, data) { (handlers[event] || []).forEach(h => h(data)); },
};
}
const mediator = createMediator();
mediator.on('userType.change', (val) => {
if (val === 'company') taxId.show();
else taxId.hide();
});
mediator.on('amount.change', (v) => {
riskNotice.toggle(v > 100000);
});
常见坑
坑 1:循环事件触发。 A 的变更触发 Mediator,Mediator 改 B,B 的变更又触发 Mediator,Mediator 又改 A —— 死循环。规范:Mediator 在处理某事件期间,标记"正在处理",阻止递归触发;或者用"防抖"避免高频回调。
坑 2:Mediator 持有所有 Colleague 强引用导致内存泄漏。 全局 EventBus 注册了一堆 listener,组件销毁时没解除注册,listener 持有组件引用,组件无法 GC。规范:组件销毁时务必 off / unsubscribe。React/Vue 这种框架在 useEffect / onUnmounted 里做清理。
坑 3:事件名拼写错误。 字符串事件名不在类型系统范围内,"userType.changed" 写成 "userTypeChanged" 没人提示。改进:用 TypeScript 强类型事件、用枚举常量、用 schema 约束。
坑 4:同步 vs 异步混乱。 Mediator 默认同步处理,但有些 handler 想 await 结果,有些 fire-and-forget。设计时明确语义,文档化。
写在最后
中介者模式的本质是"把多对多的依赖收敛到一个中心"。它不消灭复杂度,只是把复杂度搬到一个可管理的地方。这种"集中协调"在小系统里可能过度,但在大型 UI、复杂表单、微服务架构里几乎是必备 —— 没有它,系统会被无数细小的相互调用绞死。
给一个识别标志:当你画系统组件依赖图,发现箭头从所有节点指向所有节点像一团乱麻时,中介者在向你招手。引入它,N×N 的边压成 N+1,系统瞬间结构清晰。代价是 Mediator 自己可能变胖,但和混乱的依赖网比,"胖在一处"远好于"乱在所有处"。这是设计模式给你的最实在的复杂度控制工具之一。
—— 别看了 · 2026