中介者模式完全指南:从表单联动地狱到消息总线的解耦利器

中介者模式名字玄但解决的问题特别接地气:一堆对象彼此互相调用导致依赖网爆炸时,引入一个"中介",让它们只和中介通信。聊天室、表单字段联动、机场塔台、空管系统、消息总线 —— 这些场景都是中介者。这篇文章从一个真实的"表单联动地狱"讲起,看清中介者怎么把 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 仍然好得多。

什么时候用中介者

触发信号:

  1. 对象间互相调用形成网(N×N 边)。
  2. 这些对象的协作逻辑可以集中描述
  3. 对象本身的职责清晰(渲染、数据持有),但"它们之间怎么协作"是另一回事

函数式风格的中介者

// 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
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

迭代器模式完全指南:从 for-each 到 Stream / Generator 的现代演化

2026-5-15 15:29:20

技术教程

备忘录模式完全指南:从撤销重做到游戏存档与配置回滚

2026-5-15 15:35:28

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索