news 2026/9/10 7:36:38

从中介者模式到多Agent系统:Java实现与架构演变

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从中介者模式到多Agent系统:Java实现与架构演变

1. 从一次崩溃的联调说起:中介者模式到底解决了什么问题

如果你在项目里见过那种十几个对象互相newnew去、改一个需求要牵连七八个类的代码,那中介者模式就是为你准备的。

中介者模式(Mediator Pattern)是GoF 23种设计模式里行为型模式的最后一员,定义很简洁:用一个中介对象来封装一组对象之间的交互,让这些对象不再直接引用彼此。说白了,就是给一堆互相乱喊乱叫的对象中间塞一个“话事人”,所有通信都走它,谁也别想绕过它私下勾搭。

这个模式初看有点反直觉。我们平时写代码,对象A要调用对象B的方法,直接b.doSomething()不就完了吗?为什么要绕一圈?等你真正维护过一个所有模块都互相引用的项目,你就会明白直接调用带来的麻烦:对象之间耦合得像一团煮烂的面条,牵一发而动全身。中介者模式的核心价值,就是把“多对多”的混乱交互,重构成“多对一”的星型结构

适合看这篇文章的人,包括正在准备设计模式考试的学生、要交设计模式大作业的本科/研究生,以及写业务代码时总觉得类之间关系越来越乱的开发者。这篇文章会从原理讲到Java实现,再结合我在实际项目中用中介者模式的经验,聊聊这个模式在现代架构里(包括多Agent设计)的演变形态。

2. 中介者模式的结构拆解:四个角色一张星型图

2.1 四个角色的职责边界

中介者模式的类结构不复杂,但四个角色的边界一定要理清楚,不然写出来就四不像。

Mediator(抽象中介者):定义通信接口,一般会声明一个notify或者send方法,用于接收同事对象发来的消息,再转发给目标对象。

ConcreteMediator(具体中介者):实现中介者接口,内部持有所有同事对象的引用,维护交互逻辑。这是整个模式最核心也最容易写臃肿的类,因为它承载了所有业务协调逻辑。

Colleague(抽象同事类):定义同事对象的公共接口,通常持有一个中介者的引用。

ConcreteColleague(具体同事类):实现自己的业务方法,需要和其他同事通信时,不直接调用对方,而是调用自己持有的中介者对象。

我来画一个文字版的结构示意:

Mediator(抽象中介者接口) ↑ │ 实现 ConcreteMediator(具体中介者) 持有 colleagueA / colleagueB / colleagueC ↑ │ 持有引用 ┌──────────┼──────────┐ ColleagueA ColleagueB ColleagueC

注意这里的通信方向:同事类发消息给中介者,中介者再决定转发给谁。同事类之间不允许互相持有引用。这个“不允许”是模式的关键约束,破了这个规矩,整个模式就崩塌了。

2.2 一个能跑的Java聊天室案例

用聊天室来演示中介者模式是教科书里的经典做法,因为聊天室本质上就是一个中介者:用户不直接给另一个用户发消息,而是把消息发给聊天室服务器,服务器再转给目标用户。

我用Java写一个完整可运行的版本。先定义抽象同事类:

public abstract class User { protected String name; protected ChatMediator mediator; public User(String name, ChatMediator mediator) { this.name = name; this.mediator = mediator; } public abstract void send(String message); public abstract void receive(String message); public String getName() { return name; } }

然后是抽象中介者接口:

public interface ChatMediator { void sendMessage(String message, User user); void addUser(User user); }

具体中介者实现类:

import java.util.ArrayList; import java.util.List; public class ChatRoom implements ChatMediator { private List<User> users = new ArrayList<>(); @Override public void addUser(User user) { users.add(user); System.out.println(user.getName() + " 加入了聊天室"); } @Override public void sendMessage(String message, User sender) { for (User user : users) { // 不给自己发,只转发给其他用户 if (user != sender) { user.receive(sender.getName() + ": " + message); } } } }

具体同事类:

public class ChatUser extends User { public ChatUser(String name, ChatMediator mediator) { super(name, mediator); } @Override public void send(String message) { System.out.println(this.name + " 发送消息: " + message); mediator.sendMessage(message, this); } @Override public void receive(String message) { System.out.println(this.name + " 收到消息: " + message); } }

测试类:

public class MediatorDemo { public static void main(String[] args) { ChatMediator chatRoom = new ChatRoom(); User alice = new ChatUser("Alice", chatRoom); User bob = new ChatUser("Bob", chatRoom); User carol = new ChatUser("Carol", chatRoom); chatRoom.addUser(alice); chatRoom.addUser(bob); chatRoom.addUser(carol); alice.send("大家好,我是Alice"); System.out.println("---"); bob.send("欢迎Alice!"); } }

运行结果:

Alice 加入了聊天室 Bob 加入了聊天室 Carol 加入了聊天室 Alice 发送消息: 大家好,我是Alice Bob 收到消息: Alice: 大家好,我是Alice Carol 收到消息: Alice: 大家好,我是Alice --- Bob 发送消息: 欢迎Alice! Alice 收到消息: Bob: 欢迎Alice! Carol 收到消息: Bob: 欢迎Alice!

这个例子最能说明中介者的价值:如果不用中介者,Alice要发消息给Bob和Carol,就得同时持有两个用户的引用。当用户数量变成十个、二十个,每个用户都得维护一份“其他所有用户”的引用列表,新增一个用户就得改所有用户类的代码。有了中介者,用户类只认中介者一个对象,互相之间完全解耦。

2.3 关键点解读:为什么同事类只认中介者

我见过很多人写中介者模式,写着写着就变形了,最常见的问题就是同事类之间偷偷互相引用。其实中介者模式能不能起到解耦作用,全看一个关键约束:同事类之间绝对不能直接持有对方的引用

这个约束背后的逻辑是:通过中介者,把对象之间的网状关系变成星型关系。来看看两种结构的复杂度对比。

假设有n个对象,直接相互通信时,最多需要维护的连接数是 n×(n-1)/2 条,每增加一个对象,连接数线性增长。用中介者模式后,每个对象只需要维护1条到中介者的连接,总连接数只有n条,新增对象时只需要改中介者一个类。

这个差距在日常开发中感觉不明显,因为一个模块里通常也就五六个类。但如果是复杂业务系统,一个流程涉及十几二十个领域对象,网状结构就会变得完全不可维护。这也是为什么很多框架底层都在用类似的思想——把复杂的交互收敛到一个协调者里。

中介者模式的本质是封装变化:交互逻辑是变化最频繁的部分。把交互逻辑从各个同事类中抽出来,集中放到中介者类里,这样修改交互逻辑时只需要改一个类,而不是改所有参与交互的类。这是“找出变化的封装,在变化处创建边界”这个设计原则的典型应用。

3. 中介者模式的现代形态:从MVC到多Agent主从模式

3.1 你早就用过的中介者:MVC里的Controller

学设计模式最大的误区是把它当“新东西”学,其实很多模式你已经在框架里用过了,只是没对上号。

MVC架构里的Controller就是典型的中介者:View要响应用户操作时,不直接去调Model的方法,而是先把事件交给Controller,由Controller来决定要更新哪个Model、然后如何处理结果并更新View。Model和View之间没有直接依赖,全靠Controller在中间协调。这不就是中介者模式的标准结构吗?View和Model是同事类,Controller是中介者。

消息队列也是中介者模式的思想。多个服务要通信,不直接互相调接口,而是把消息发到消息队列中间件,由中间件路由转发。这样一来,服务之间完全解耦,新增一个消费方不需要改任何生产方的代码。大型分布式系统里,服务数量动辄几十上百个,没有消息队列做中介,服务间的调用关系会变成一张无法维护的蜘蛛网。

前端状态管理工具,比如Redux,也是中介者模式。组件要改全局状态时,不直接操作其他组件,而是dispatch一个action给store,store里的reducer处理完状态变化后再通知所有订阅的组件更新。组件与组件、组件与状态之间完全通过store中转。

3.2 热词观察:多Agent设计里的“主从模式”与中介者

最近“多Agent系统”这个概念特别火,尤其在AI应用开发领域。我在看多Agent设计相关资料时注意到,智能体设计模式里经常提到“主从模式”:一个主Agent负责调度,多个子Agent(subagent)各司其职。

然后有人提出一个很有洞察的看法:主从模式本质上就是把subagent当作一种另类的tool来调用。

这个观察很有意思,因为它直接联系到了行为型模式的本质。主Agent持有所有子Agent的注册信息,接收来自用户或其他模块的任务,然后决定把任务分发给哪个子Agent,子Agent处理完把结果返回给主Agent。这个结构,就是中介者模式在Agent系统里的翻版。各subagent不需要互相知道对方的存在,更不需要直接调用对方的能力,所有协作路径都要经过主Agent协调。

不仅仅是主从模式,Agent系统里的几种常见结构都能看到中介者模式的影子:

轮询协调结构:一个核心Agent按照顺序或优先级依次询问各个专业Agent是否需要处理当前任务,这相当于具体中介者接收同事对象的请求后,通过遍历方式决定转发给谁。

路由分发结构:核心Agent根据任务类型把请求路由给对应能力Agent,这相当于中介者模式里sendMessage方法内部的逻辑判断——根据消息类型和目标对象决定转发路径。

共享状态结构:多个Agent通过共享外部存储来交换信息,这个存储就是中介者。Agent不需要知道谁生产了数据,只要往共享区放或从共享区取就行。

如果你要写设计模式大作业,围绕“中介者模式在多Agent系统中的应用”来做,会是一个非常切合技术热点、又有足够深度的选题。思路可以是这样:设计一个任务调度Agent作为中介者,把代码生成、文档撰写、代码审查三个专项Agent作为注册的同事对象,任务调度Agent接收用户请求后,分发给对应专项Agent,专项Agent处理完把结果返回给调度Agent。这个案例既展示了中介者模式的结构,又扣住了智能体设计这个热门领域。

3.3 中介者模式与观察者模式、门面模式的区别

学设计模式时最容易搞混的就是中介者模式、观察者模式和门面模式,因为它们都涉及解耦,但解耦的层次和方向各不相同。

观察者模式解决的是“一对多通知”的问题:一个主题对象状态变化,要通知多个观察者。通信方向是单向的——从主题到观察者。中介者模式解决的是“多对多交互”的问题:多个同事对象之间互相通信,通信方向是双向的——任何一个同事都可以发消息给任何一个其他同事。

我用一个对比场景来说明。新闻订阅系统,报社(Subject)发布新闻,多个订阅者(Observer)接收新闻,这是观察者模式。即时通讯系统,任何一个用户都可以给其他任何用户发消息,所有消息都经过服务器(Mediator)转发,这是中介者模式。关键区别在通信方向:有一个源头,是观察者模式;多对多互相通信,是中介者模式。

门面模式(Facade)和中介者模式表面上也有点像:都是在一个复杂系统外面包一层。但门面模式暴露的是一个简化后的接口,它的目的不是管理对象间的交互,而是给客户端一个简单入口,客户端还是可以绕过门面直接访问子系统。中介者模式则不允许同事对象绕过中介者直接通信,它管理的不只是接口,而是完整的交互逻辑。

写大作业或者面试梳理时,把这个对比讲清楚,能体现你对设计模式理解得比较透彻。我整理了一个表格方便对照:

维度中介者模式观察者模式门面模式
核心目的封装多对多交互实现一对多通知简化外部访问接口
通信方向双向,任意同事可发可收单向,主题向观察者推送单向,客户端访问子系统
对象关系同事间不直接引用观察者订阅主题客户端依赖门面
管理内容交互逻辑状态变化通知接口暴露
模式类型行为型行为型结构型

4. 中介者模式的代价与决策:什么时候别硬上

4.1 中介者膨胀:所有逻辑堆成一个上帝类

中介者模式最知名的问题就是“中介者膨胀”。交互逻辑都收拢到中介者类里,随着业务需求持续增加,这个类会变得越来越臃肿,最后变成一个什么都要管、什么都耦合的“上帝类”。

我在一个订单系统里就踩过这个坑。最初用中介者统一管理订单、库存、支付、物流四个模块的交互,写起来确实清爽。但三个月后需求叠加,中介者类里塞了二十多个方法,将近一千行代码,方法之间共享大量私有状态,改一个逻辑要担心影响其他八个逻辑。

后来我做了拆分:按业务子域把大的中介者拆成几个小中介者,比如订单协调器、支付协调器、物流协调器,各管各的交互,子协调器之间再通过一个上层协调器串起来。这本质上不是推翻中介者模式,而是给模式加了层次,避免单个中介者无限膨胀。

这个问题的根源在于:中介者模式把交互逻辑集中了,但集中不等于可以无限堆叠。当交互逻辑本身变得复杂时,中介者内部也要做模块化,而不是把所有东西平铺在一个类里。这和我们写普通代码时做职责划分的原则是一样的,模式只是约束了类之间的拓扑关系,并没有免去你做好内聚的职责。

4.2 性能损耗与调试成本:消息多绕了一圈

中介者模式引入了一个间接层,消息从一个同事对象到另一个同事对象,必须多经过一次转发。这个性能损耗在普通业务系统里几乎可以忽略不计,但如果通信频率非常高、消息量非常大,中转路径就会成为瓶颈。

我在一个实时对战服务器里做过测试:场景是一百个玩家实时同步位置,如果玩家之间直接通信,每个消息一跳就能到达目标;经过中介者转发后,消息要经过中心节点再分发到目标,网络开销差不多翻倍。对于实时性要求极高的场景,这种间接层就要慎重。

调试成本是另一个要考虑的点。直接通信时,调用链很清晰,出问题顺着调用栈就能找到源头。中介者模式下,消息在同事类和中介者之间来回传递,调用栈会变深,排查问题时要多绕一圈。我记得有一次线上事故,一个消息在中介者里被转发错了对象,找问题的过程相当痛苦,因为各个同事类的方法日志都显示“消息已发出”,只有把中介者的转发日志全部拉出来比对,才定位到是判断条件写错了。

但要注意,这些问题不是“模式不好”,而是“模式不适合这个场景”。中介者模式的收益在可维护性上,代价在运行效率和直接性上。当你觉得“这个模式写起来好绕”的时候,不妨停下来想想:你是真的需要这种解耦,还是只是在硬套模式。

4.3 一个务实的决策清单

我用中介者模式之前,会过一遍下面的清单,帮自己做出判断:

判断条件适合用中介者不建议用中介者
交互对象数量3个及以上只有2个
交互是否多对多是,互相通信否,只有单向调用
交互逻辑改动频率频繁,每次改动影响多个对象稳定,很少变化
业务逻辑复杂度复杂度高,需要封装简单直接,一眼看懂
对性能敏感度不敏感,可接受一次转发极敏感,需要最短路径

两个对象之间的通信,直接用引用就好,硬套中介者模式只会增加代码量、降低可读性,这是“过度设计”。三到四个对象交互,并且交互逻辑经常需要调整,中介者模式就很合适。二三十个对象交互,如果不是中介者模式,别的方案也很难收拾这个复杂度,但要注意中介者的内聚问题,不要把一个类写成两三千行。

记住一点:设计模式是解决问题的工具,不是用来炫技的。如果一段代码用普通写法清晰又简单,就不用为了“符合模式”去硬拗结构。中介者模式真正要解决的问题是“对象越多,交互越乱”,而不是“代码看起来不够设计感”。

5. 常见问题与排查技巧实录

5.1 高频问题速查表

我在学习、使用和帮助同学排查中介者模式相关代码时,整理了一些高频问题,做成一个速查表供参考:

问题表现可能原因解决方案
编译报错,找不到中介者方法抽象同事类里持有的是抽象中介者类型,但调用了具体中介者才有的方法把需要同事类调用的方法都定义到抽象中介者接口里,或者将持有的类型改为具体中介者
所有同事都收到了消息,但只需要发给部分目标中介者转发时使用了广播逻辑,没有做目标筛选在消息中携带目标信息,中介者收到后先判断再转发,就像聊天室里user != sender那个判断
同事类之间还是在直接调用对模式理解不够,图省事直接引用了对方对象强制约束,代码审查时检查同事类中是否存在其他同事类的引用
中介者类越来越大,改起来越来越怕交互逻辑全部堆在一个类里,内聚性崩塌按业务子域拆分中介者,上层中介者协调下层中介者,形成有层次的中介结构
消息丢失,没有任何转发日志中介者内部逻辑异常,被吞掉了异常或提前 return在中介者转发入口和出口都加日志,消息进和出分开记录
初始化顺序问题,同事类构造时就需要发消息,但中介者还没准备好构造顺序不当先创建中介者,再创建同事类并把中介者传给同事类;如果同事类需要在构造时发消息,要确认中介者已经完成初始化

5.2 实操心得:怎么调试和避坑

调试中介者模式代码,我有个习惯:给中介者的入口和出口都加日志。入口就是sendMessage这类方法的开头,记录“谁发了什么消息进来”,出口就是实际转发调用的地方,记录“把这条消息转发给了谁”。这两个日志一对,就能快速判断问题出在同事类还是中介者。

还有一个小技巧,把中介者的核心转发逻辑单独抽一个方法出来,比如route(User sender, String message, Class<?> targetType),用路由这个词来命名,思路会清晰很多。因为中介者本质上就是在做消息路由:接收消息,根据规则决定转发给谁。把路由逻辑独立出来之后,新增转发规则就只需要改这一个方法。

排查“收不到消息”的问题时,很多人会盯着同事类的receive方法找原因,其实大概率问题出在事件监听机制上。检查中介者是否把所有需要接收消息的同事都注册进来了。漏注册一个对象,就跟开会没人通知你一样,你还以为是网络问题,其实是压根不在通知名单里。我排查过一次,最后发现是addUser方法在某个分支里提前 return 了,导致一个人没被加进列表,花了两个小时才定位到。

另一个容易踩的坑是把中介者做成全局单例后,状态没清理干净。尤其是用注册列表保存同事对象时,如果同事对象需要被销毁,而中介者的列表里还保留着引用,会造成内存泄漏。我看到过因为聊天室功能关闭但用户列表没清空,导致内存占用一直涨的案例。解决方案是给中介者加一个removeUser方法,对象销毁时通知中介者把它从列表里移除。

5.3 大作业和面试里的加分写法

如果你是拿中介者模式做设计模式大作业,或者准备面试时被问到中介者模式,有几个点可以额外深入。

大作业方面,别只写聊天室,太俗了,面试官每年都要看几百个聊天室案例。可以试试这些更有区分度的方向:电梯调度系统(多部电梯与多个楼层按钮交互)、机场塔台系统(多架飞机与多条跑道协调)、智能家居中央控制器(多个设备协同,比如回家模式要同时开灯、开空调、播放音乐)。尤其是智能家居这个方向,跟现在物联网热点贴合紧密,又能把中介者模式的优点展示得很充分——设备各司其职,全部通过中央控制器协调。

面试方面,建议主动讲清楚三个东西:第一,中介者模式解决了什么问题,用图或例子说明“多对多的引用关系变成了星型关系”;第二,它有什么代价,能说出中介者膨胀问题才是真懂;第三,用一个你实际写过或见过的场景举例,说明为什么在那个场景里需要中介者模式。能把这三个问题讲透,面试官基本不会觉得你只会背定义。

还有一点可以提:中介者模式和“最小知识原则”(Law of Demeter,也叫迪米特法则)的关系。中介者模式是该原则的典型实践——每个对象只和它直接认识的对象通信,不通过第三者认识其他对象。讲清楚这一层,说明你对设计原则和设计模式之间的关联有自己的思考,这是从“会用模式”到“理解模式”的分水岭。

6. 我的个人体会

我现在写代码时,每次发现某个类里同时出现了三四个其他类的方法调用,就会本能地停下来想想:是不是应该在这里引入一个中介者?这个习惯就是学了中介者模式之后养成的。

这个模式让我重新理解了软件设计里两个重要的词,一个是“间接”,一个是“收敛”。直接调用确实快,但间接层带来的是灵活性和可维护性。中介者模式就是用一个可控的间接层,把无处不在的调用关系收敛到一个点,让混乱变得有序。它解决的问题不是“代码能不能跑”,而是“代码能不能改”。

包括现在很火的多Agent系统设计,我看到那些主打成模式的时候,第一反应就是:这不就是中介者模式在新的技术形态下换了件衣服吗?技术的形态一直在变,但设计思想是稳定的。学会透过现象看模式,比背下二十三个模式的代码要重要得多。这也是设计模式真正值得花时间学习的原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 7:35:06

DBeaver 如何通过扩展点注册新的 AI 引擎与助手

DBeaver 如何通过扩展点注册新的 AI 引擎与助手 【免费下载链接】dbeaver Free universal database tool and SQL client 项目地址: https://gitcode.com/GitHub_Trending/db/dbeaver DBeaver 的 AI 能力&#xff08;补全、SQL 生成、助手问答&#xff09;并不写死在核心…

作者头像 李华
网站建设 2026/9/10 7:34:12

基于类Sagnac干涉仪的矢量光束生成与VirtualLab Fusion仿真

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 7:33:12

PHP内存溢出排查与优化:从报错定位到分批处理实战

平时在群里看PHP开发者问得最多的问题&#xff0c;除了"这个报错啥意思"&#xff0c;大概就是内存溢出了。尤其像批量Excel处理、远程接口拉取大数据、图片压缩、视频处理这类脚本&#xff0c;跑着跑着突然弹出一句&#xff1a;PHP Fatal error: Allowed memory size…

作者头像 李华
网站建设 2026/9/10 7:32:52

2020电赛E题资料.zip深度解析:嵌入式实时信号处理实战指南

简介&#xff1a;本资源为2020年全国大学生电子设计竞赛E题&#xff08;无线充电器设计&#xff09;的完整实战资料包&#xff0c;面向电子类、自动化、通信等专业本科生及电赛备赛团队&#xff0c;聚焦高频功率变换、磁耦合谐振建模与闭环控制实现等核心难点。压缩包共200.77M…

作者头像 李华
网站建设 2026/9/10 7:32:31

Buzz 上手指南:本地离线转录,免费把录音转成文字和字幕

Buzz 上手指南&#xff1a;本地离线转录&#xff0c;免费把录音转成文字和字幕 【免费下载链接】buzz Buzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper. 项目地址: https://gitcode.com/GitHub_Trending/buz/buzz …

作者头像 李华
网站建设 2026/9/10 7:32:01

2026大模型工程师实战路线:从本地部署到微调落地的技能地图

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华