你用了五年的消息队列,不知道它背后站着中介者模式

你用了五年的消息队列,不知道它背后站着中介者模式

先别急着翻 GoF 目录。我说一个你天天在用、但从没意识到它是设计模式的东西:消息队列。

不是比喻。消息队列就是中介者模式(Mediator)的分布式实现。Producer 和 Consumer 不直接通信,所有消息经过 Broker 中转------这就是中介者模式的灵魂:用一个中介对象封装一组对象的交互方式,让它们不直接引用彼此。

一个没有中介者的聊天室

假设你写过一个简单的群聊功能。最开始你可能这么写:

java 复制代码
public class User {
    private String name;
    private List<User> contacts; // 直接持有其他用户引用
    
    public void send(String message) {
        for (User u : contacts) {
            u.receive(this.name, message);
        }
    }
}

功能没问题。现在需求来了:有人退群怎么办?要通知所有人刷新列表。有人被禁言?只发消息给管理员。有人发敏感词?先过一遍过滤规则再决定是否投递。

上面的代码每加一个需求就要改 User 类------消息路由、过滤、黑名单全塞在一起。三个月后 User.send() 变成了 200 行的 if-else 地狱。更糟的是,User 之间互相持有引用,退群时要遍历所有 User 删引用,漏一个就是内存泄漏。

中介者模式怎么解决?把通信逻辑抽出来,放进一个独立的 ChatRoom 类。

java 复制代码
public class ChatRoom {
    private Map<String, User> users = new HashMap<>();
    
    public void register(User user) {
        users.put(user.getName(), user);
    }
    
    public void send(String from, String to, String message) {
        User recipient = users.get(to);
        if (recipient != null && !recipient.isMuted()) {
            recipient.receive(from, message);
        }
    }
    
    public void broadcast(String from, String message) {
        for (User u : users.values()) {
            if (!u.getName().equals(from) && !u.isMuted()) {
                u.receive(from, message);
            }
        }
    }
}

public class User {
    private String name;
    private ChatRoom room; // 只持有中介者引用
    
    public void sendTo(String to, String message) {
        room.send(this.name, to, message);
    }
    
    public void sendAll(String message) {
        room.broadcast(this.name, message);
    }
}

User 从互相引用的网状结构变成只依赖 ChatRoom 的星型结构。加禁言功能只改 ChatRoom,User 一行不动。这就是中介者模式的核心价值:把 M×N 的交互复杂度降为 M+N。

跟观察者模式的区别------很多人搞混

中介者模式和观察者(Observer)模式都涉及对象间的通信,但方向完全不同:

  • 观察者:Subject 不知道 Observer 是谁,只管"有事件发生了,你们自己看着办"。通信是单向广播。
  • 中介者:通信双方都对 Mediator 发指令,Mediator 决定怎么路由、怎么协调。通信是多向的、需要协调的。

聊天室里,A 发消息给 B,B 的回复可能要通知 C,这个过程中 ChatRoom 做决策。这就是中介者,不是观察者。

消息队列里的死信队列、延迟投递、消息过滤------这些都是 Mediator 的协调逻辑,不是 Observer 能做到的。

消息队列就是中介者模式的工业级实现

回头看消息队列的架构:

css 复制代码
Producer A ──→             ──→ Consumer X
Producer B ──→  [Broker]   ──→ Consumer Y
Producer C ──→             ──→ Consumer Z

这就是一个标准的 Mediator。Broker 居中协调,生产者和消费者彼此不知道对方的存在。

RocketMQ 里的 Tag 过滤、顺序消息、事务消息------这些都是 Mediator 在"封装交互"。如果没有 Broker 这个中介者,你需要在每个 Producer 和每个 Consumer 之间建立直连,连接数瞬间爆炸。

我在一个订单系统里见过这种惨状。早期架构里订单服务直接调用库存、支付、物流三个服务------四个服务六对调用关系。后来加了优惠券和积分服务,变成十五对调用关系。每改一个服务的接口,要改四五个调用方。

上了消息队列之后,订单服务只管往 Topic 发消息。库存、支付、物流各自订阅感兴趣的 Topic。订单服务不需要知道消费者的存在,甚至连消费者有几个都不关心。

这不是架构演进,这是中介者模式在工程层面的自然表达。

Spring 里到处都是中介者的影子

Spring 的 ApplicationEvent 机制本质上就是中介者模式:

java 复制代码
@Component
public class OrderMediator {
    @Autowired
    private ApplicationEventPublisher publisher;
    
    public void placeOrder(Order order) {
        // 业务逻辑...
        publisher.publishEvent(new OrderPlacedEvent(order));
        // ApplicationContext 作为中介者,负责把事件路由给所有监听器
    }
}

@Component
public class InventoryListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // 扣库存 ------ 不需要知道谁发的,只关心事件本身
    }
}

@Component
public class NotificationListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // 发通知 ------ 同上
    }
}

ApplicationContext 就是那个隐形的 Mediator。你不需要在 OrderService 里注入 InventoryService 和 NotificationService,你只管发事件。谁监听、怎么处理、有没有异常------这些协调逻辑被 Spring 容器封装了。

DispatcherServlet 也是。它居中协调所有 HandlerMapping、HandlerAdapter、ViewResolver,Controller 只需要关心自己的业务逻辑,完全不知道 HTTP 请求是怎么被路由到自己这里的。

三个真实的坑

坑一:上帝中介者。

把太多协调逻辑塞进 Mediator,它就变成了"上帝对象"------什么都管,什么都依赖,改一行影响全局。

见过一个 ChatMediator 管了:消息路由、敏感词过滤、用户权限、消息持久化、未读数统计、@提醒、文件上传、表情解析。这个类是代码审查禁区------没人敢动。

解法:中介者也可以分层。ChatMediator 只做路由,FilterChain 做过滤,MessageStore 做持久化,NotificationService 做提醒。每个 Mediator 有自己的职责边界。

坑二:不该中介的地方用了中介者。

两个对象之间的简单调用不需要中介者。订单服务调用支付服务,就一个调用方,你引入消息队列变成异步------除非有削峰需求,否则只是增加延迟和复杂度。

判断标准:当交互涉及三个以上对象,或者交互逻辑频繁变化时,才引入 Mediator。 两个对象、逻辑稳定的场景,直接调用比中间加一层 Broker 快得多。

坑三:消息队列不是银弹。

有团队一说解耦就上消息队列,结果一个 CRUD 系统里跑着五个 Topic,排查问题时要在多个服务之间跳转。消息队列解了对象耦合,但引入了时间耦合------你现在要关心消息丢失、重复消费、顺序性、积压告警。

中介者模式的核心是"把交互逻辑集中管理",不是为了解耦而解耦。如果你的交互逻辑本身就不复杂,直调可能是更好的选择。

什么时候该用

中介者模式最适合三种场景:

  1. 对象间的交互逻辑频繁变化。比如群聊里的禁言、黑名单、消息撤回------规则一直在变,集中管比分散在 User 里好维护一百倍。

  2. 需要复用交互组件,但交互方式因场景不同。GUI 框架里,同一个 Button 在对话框和表单里的行为不同------Mediator 换一下就行,Button 本身不用改。

  3. 减少子系统间的直接依赖。微服务里的消息队列就是典型案例。订单服务不需要知道库存服务的 API 签名,只需要知道 Topic 名称。

不一致的团队不要用。中介者模式把逻辑集中了,但也把"理解成本"集中了。如果你团队里只有一个人懂那个 Mediator,他一走项目就瘫痪。

一句话总结

中介者模式不是让你"加个中间层",是让你把对象间混乱的交互关系,封装成一个可独立维护的协调单元。消息队列、Spring 事件、GUI 框架------都在做这件事,只是没人叫它设计模式。


我在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」,23 个设计模式用漫画加答题的方式讲。中介者模式在里面的漫画场景是"卡皮巴拉餐厅的传菜员"------厨师和顾客不直接交流,传菜员居中协调。感兴趣的话搜一下「爪爪代码冒险记」。

相关推荐
Gopher_HBo1 小时前
路由注册:RouterGroup(routergroup.go)
后端
用户608186527901 小时前
Avalonia UI 控件样式定义的三种方式详解
后端
feng尘1 小时前
volatile 可见性与内存屏障知识点
后端
SamDeepThinking1 小时前
第3篇:企业级CAS单点登录实战-技术架构设计方案
后端·程序员·架构
JoyT1 小时前
Agent 开源项目全景解析(上):LangGraph、Spring AI 与 Agent Runtime
后端
云技纵横1 小时前
线上接口突然超时,怎么判断卡在 Nginx、线程池、连接池还是 SQL?
后端·sql·mysql
Java内核笔记1 小时前
容错能力进入 spring-core:Spring Boot 4 原生重试机制全解析
java·后端
风卿1 小时前
知识库双路召回:BM25 关键词与语义向量 RRF 融合,附指标实测
后端
未秃头的程序猿1 小时前
虚拟线程上线一周后翻车了——pinning问题排查实录
java·后端·架构