概述
中介者模式(Mediator Pattern),正如其名,扮演着一个调停者、中间人的角色。GoF将其核心意图定义为:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
在复杂的软件系统中,对象间的通信往往容易演变成一张混乱的、难以维护的“蜘蛛网”(网状依赖)。每一个对象都需要知道它要与哪些其他对象通信,导致代码高度耦合、复用性差、牵一发而动全身。中介者模式正是为解决这一核心痛点而生:它将多对象之间复杂的网状依赖关系,转变为以中介者为中心的星型拓扑结构。通过将通信逻辑集中化管理,中介者模式不仅实现了组件间的松耦合,还使得交互流程变得清晰、可控且易于修改。
本文将为您呈现一条从理论到实践、从单机到分布式的完整知识脉络。我们将首先从混乱的组件互调场景出发,通过代码演进直观感受中介者如何化繁为简;继而深入JDK、Spring、Netty等经典框架源码,剖析大师们是如何运用这一模式的;最后,我们将视野拓展至分布式环境,揭示消息中间件、注册中心乃至API网关背后蕴含的中介者思想。此外,本文还将提供大量可直接运行的代码示例、清晰易懂的Mermaid图表,并结合专家级面试题,助您彻底掌握这一重要设计模式。
一、模式定义与结构
1.1 GoF标准定义
中介者模式(Mediator Pattern):定义一个对象,该对象封装了一组对象如何交互。中介者通过防止对象显式地相互引用来促进松散耦合,并且它允许我们独立地改变它们的交互。
1.2 UML类图(Mermaid)
classDiagram
class Mediator {
<<interface>>
+notify(sender: Colleague, event: String)
}
class ConcreteMediator {
-colleagues: List~Colleague~
+notify(sender: Colleague, event: String)
+addColleague(colleague: Colleague)
}
class Colleague {
<<abstract>>
#mediator: Mediator
+Colleague(mediator: Mediator)
+send(event: String)
+receive(message: String)
}
class ConcreteColleagueA {
+send(event: String)
+receive(message: String)
+operationA()
}
class ConcreteColleagueB {
+send(event: String)
+receive(message: String)
+operationB()
}
Mediator <|.. ConcreteMediator : 实现
Colleague <|-- ConcreteColleagueA : 继承
Colleague <|-- ConcreteColleagueB : 继承
ConcreteMediator o-- Colleague : 管理
ConcreteColleagueA --> Mediator : 持有引用
ConcreteColleagueB --> Mediator : 持有引用
1.3 类图解析与星型拓扑转换
上述UML类图清晰地勾勒出了中介者模式的四大核心角色。在未引入中介者之前,假设系统中有 ConcreteColleagueA 和 ConcreteColleagueB 等多个同事对象,它们为了互相通信,必须持有彼此的引用。例如,A 想要调用 B 的方法,A 内部必须包含一个 B 的实例。随着同事对象的增加(如加入 C、D、E),对象间的引用关系将呈现复杂的网状结构(Mesh Topology)。这种结构的问题在于:任何一个对象的变化都可能波及所有与之关联的对象,系统的可维护性和可扩展性极差。
中介者模式的核心精妙之处,在于通过引入 ConcreteMediator 角色,将原本复杂的网状通信拓扑,重构为以中介者为中心的星型通信结构(Star Topology)。观察类图可见,ConcreteColleagueA 和 ConcreteColleagueB 不再直接持有彼此的引用,而是各自持有 Mediator 接口的引用。当 A 需要与 B 通信时,A 调用 mediator.notify(this, "eventForB"),将消息和自身引用发送给中介者。中介者 ConcreteMediator 内部维护了所有同事对象的集合,它在接收到 A 的通知后,解析事件内容,决定将消息路由给 B,并调用 B 的 receive 方法。如此一来,同事对象之间从“多对多”的直接依赖,转变为了“多对一”依赖中介者,再由中介者进行“一对多”的分发。这种星型结构极大地降低了系统耦合度,同事类只需关心自己的业务逻辑和如何与中介者交互,而复杂的协作逻辑被完全封装在中介者内部。
各角色职责详解:
- Mediator(抽象中介者):声明了用于与各同事对象通信的接口。通常包含一个如
notify(Colleague sender, String event)的方法,用于接收同事对象发来的事件。 - ConcreteMediator(具体中介者):实现了
Mediator接口。它持有并维护所有同事对象的引用,协调各个同事对象之间的交互逻辑。它是模式的核心,封装了原本分散在各个同事对象中的协作规则。 - Colleague(抽象同事类):持有对中介者对象的引用。它定义了同事类的公共行为,通常包含
send和receive方法。同事对象之间不允许直接通信,必须通过中介者。 - ConcreteColleague(具体同事类):实现自身的业务逻辑。当需要与其他对象交互时,调用中介者的方法;当收到中介者转发来的消息时,执行相应的
receive逻辑。
二、代码演进与实现
为了直观展示中介者模式的价值,我们以经典的聊天室场景为例进行代码演进。
2.1 不使用模式的原始代码:混乱的网状依赖
假设我们有一个简单的聊天系统,User 对象可以直接向其他 User 发送消息。
import java.util.ArrayList;
import java.util.List;
// 原始User类:直接持有其他用户的引用
class UserV1 {
private String name;
private List<UserV1> contacts = new ArrayList<>(); // 网状依赖的根源
public UserV1(String name) {
this.name = name;
}
public void addContact(UserV1 user) {
contacts.add(user);
}
// 直接遍历联系人列表发送消息
public void sendMessage(String message) {
System.out.println(this.name + " 发送消息: " + message);
for (UserV1 contact : contacts) {
contact.receiveMessage(this, message);
}
}
public void receiveMessage(UserV1 sender, String message) {
System.out.println(this.name + " 收到来自 " + sender.getName() + " 的消息: " + message);
}
public String getName() {
return name;
}
}
// 客户端调用演示
public class NoMediatorDemo {
public static void main(String[] args) {
UserV1 alice = new UserV1("Alice");
UserV1 bob = new UserV1("Bob");
UserV1 charlie = new UserV1("Charlie");
// 建立混乱的网状关系:每个人都要显式添加好友
alice.addContact(bob);
alice.addContact(charlie);
bob.addContact(alice);
bob.addContact(charlie);
charlie.addContact(alice);
charlie.addContact(bob);
alice.sendMessage("大家好!");
}
}
问题分析:
- 耦合度高:每个
UserV1对象必须维护一个contacts列表,即必须知道所有潜在通信对象的引用。若系统有成千上万用户,内存占用和关系维护将是灾难。 - 交互逻辑分散:消息发送的逻辑(遍历列表)硬编码在
UserV1类中。如果需要实现“仅向管理员发送”或“黑名单过滤”等功能,必须修改UserV1类,违反了单一职责原则和开闭原则。 - 新增组件困难:若想加入一个新的组件(如
ChatBot),它必须也去实现addContact逻辑,并修改所有相关类的引用关系。
2.2 经典中介者模式重构:引入聊天室中介
现在,我们引入 ChatRoomMediator 来解耦用户关系。
import java.util.HashMap;
import java.util.Map;
// 1. 定义中介者接口
interface ChatMediator {
void sendMessage(String message, User sender, String receiverName); // 支持定向消息
void addUser(User user);
}
// 2. 实现具体中介者
class ChatRoomMediator implements ChatMediator {
// 维护同事对象集合
private Map<String, User> users = new HashMap<>();
@Override
public void addUser(User user) {
this.users.put(user.getName(), user);
System.out.println("[中介者] 用户 " + user.getName() + " 加入聊天室");
}
@Override
public void sendMessage(String message, User sender, String receiverName) {
// 核心协调逻辑:转发消息
if (receiverName == null || receiverName.isEmpty()) {
// 广播模式:发给除发送者外的所有人
System.out.println("[中介者] 收到来自 " + sender.getName() + " 的广播: " + message);
for (User user : users.values()) {
if (!user.getName().equals(sender.getName())) {
user.receive(message, sender.getName());
}
}
} else {
// 单播模式
User receiver = users.get(receiverName);
if (receiver != null) {
System.out.println("[中介者] 转发来自 " + sender.getName() + " 给 " + receiverName + " 的私聊: " + message);
receiver.receive(message, sender.getName());
} else {
System.out.println("[中介者] 用户 " + receiverName + " 不在线");
}
}
}
}
// 3. 抽象同事类
abstract class User {
protected ChatMediator mediator;
protected String name;
public User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public abstract void send(String message, String receiverName);
public abstract void receive(String message, String senderName);
public String getName() {
return name;
}
}
// 4. 具体同事类
class ChatUser extends User {
public ChatUser(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String message, String receiverName) {
System.out.println(this.name + " 发送请求: " + (receiverName == null ? "广播" : "私聊给 " + receiverName));
// 关键点:不直接发给receiver,而是发给中介者
mediator.sendMessage(message, this, receiverName);
}
@Override
public void receive(String message, String senderName) {
System.out.println(this.name + " 的聊天窗口: [" + senderName + "]: " + message);
}
}
// 5. 客户端演示
public class MediatorPatternDemo {
public static void main(String[] args) {
ChatMediator mediator = new ChatRoomMediator();
User alice = new ChatUser(mediator, "Alice");
User bob = new ChatUser(mediator, "Bob");
User charlie = new ChatUser(mediator, "Charlie");
// 注册同事对象
mediator.addUser(alice);
mediator.addUser(bob);
mediator.addUser(charlie);
System.out.println("--- 广播场景 ---");
alice.send("大家好,有人吗?", null);
System.out.println("\n--- 私聊场景 ---");
bob.send("Hi Alice,我在呢", "Alice");
}
}
2.3 中介者模式的进阶特性
在上述经典实现基础上,我们可以进一步深化中介者的能力。
a. 广播与定向通信
在 ChatRoomMediator 中已经体现,通过判断 receiverName 是否为空来实现广播或单播。这种逻辑集中在中介者中,使得同事类(User)无需关心具体是广播还是单播,只需要调用 send 方法即可。
b. 同事对象的动态注册与注销
ChatRoomMediator 维护了 Map<String, User>,天然支持运行时添加用户(addUser)。同理,只需添加 removeUser(String name) 方法,即可在运行时动态注销同事,中介者将不再向该对象转发消息。这在插件化架构中极为有用。
c. 与观察者模式的结合:事件总线
中介者本身可以作为事件总线。我们可以让中介者不仅转发简单消息,而是转发不同的事件对象。
// 事件基类
class ChatEvent {
private String type;
// getter/setter...
}
// 中介者增加事件发布接口
interface EventMediator {
void publishEvent(ChatEvent event, User publisher);
void subscribe(String eventType, User subscriber);
}
// 同事类可以订阅感兴趣的事件,中介者内部维护订阅关系(类似Guava EventBus或Spring事件机制)。
// 这种结合使得中介者不仅是通信管道,更是系统内的事件调度中心。
2.4 通信时序图(Mermaid)
以下时序图展示了 Alice 通过中介者向 Bob 发送私聊消息的完整调用流程。
sequenceDiagram
participant Alice as ColleagueA<br/>(Alice)
participant Mediator as ConcreteMediator<br/>(ChatRoom)
participant Bob as ColleagueB<br/>(Bob)
Alice->>Mediator: 1. sendMessage("Hello", this, "Bob")
Note right of Alice: 调用中介者接口
activate Mediator
Mediator->>Mediator: 2. 查找接收者 "Bob"<br/>执行业务校验(如是否在线)
alt 接收者存在
Mediator->>Bob: 3. receive("Hello", "Alice")
activate Bob
Bob->>Bob: 4. 显示消息或执行业务逻辑
deactivate Bob
else 接收者不存在
Mediator-->>Alice: 返回错误或记录日志
end
deactivate Mediator
2.5 时序图文字说明
上述时序图精准地揭示了中介者模式下的消息流转路径,这种路径与传统的直接调用有本质区别,具体分析如下:
-
发起调用(步骤1):作为发送方的
ColleagueA(Alice),其内部不再持有ColleagueB(Bob)的直接引用,而是持有Mediator的引用。Alice 调用中介者的sendMessage方法,并将自身引用this和目标标识"Bob"作为参数传递。这一步是关键解耦动作:Alice 只知道自己要和 "Bob" 通话,但并不知道 Bob 对象在内存中的具体位置或状态,她完全信赖中介者。 -
中介者仲裁(步骤2):
ConcreteMediator(ChatRoom)接收到请求后,接管了全部控制权。它执行了原本分散在客户端代码中的协调逻辑:- 对象定位:根据标识
"Bob"从内部维护的注册表(Map)中查找对应的同事对象实例。 - 策略执行:在调用 Bob 之前,中介者可以插入任意复杂的业务规则。例如,检查 Bob 是否处于“勿扰模式”、记录通信日志用于审计、检查 Alice 是否在黑名单中、甚至将消息翻译成其他格式。
- 错误处理:如果查找失败或校验不通过,中介者可以直接返回错误给 Alice,而 Alice 无需关心具体的错误原因。
- 对象定位:根据标识
-
目标响应(步骤3与4):一旦中介者决定放行,它调用目标同事
ColleagueB的公开接口receive。对于 Bob 而言,它只知道自己收到了来自 Alice 的消息,但它并不清楚这条消息是 Alice 直接发来的,还是通过中介者转发的。这种透明性使得 Bob 的代码也无需依赖 Alice。
整个时序过程完美诠释了“星型拓扑”:所有消息流都经由中心节点(Mediator)汇聚和分发,边缘节点(Colleague)只需与中心节点交互,从而彻底消除了边缘节点之间的横向耦合。
三、源码级应用分析
中介者模式在各大经典框架中都有深度的应用体现,理解这些实现有助于我们掌握模式的精髓。
3.1 JDK案例
1. java.util.Timer
Timer 类是 JDK 中一个典型的中介者。它作为调度中介者,协调多个 TimerTask(同事对象)的执行。
- Mediator:
Timer。它内部维护了一个任务队列TaskQueue和一个执行线程TimerThread。 - Colleague:
TimerTask。 - 交互逻辑:用户创建多个
TimerTask并提交给Timer(timer.schedule(task, delay))。Timer作为中介者,统一管理这些任务的执行时机和线程调度。TimerTask之间互不知晓,它们只负责定义要执行的任务内容,而何时执行、以何种顺序执行,完全由中介者Timer决定。这避免了开发者需要手动管理多线程调度和任务依赖的复杂性。
2. java.lang.reflect.Method.invoke()
反射机制本身就是一种宏大的中介者模式体现。JVM 充当了终极中介者。
- Mediator: JVM 的类加载器、方法区数据结构。
- Colleague: 调用者对象 与 被调用者对象(以及 Method 对象代表的代码逻辑)。
- 交互逻辑:调用者并不直接持有被调用者的强类型引用,而是通过
Method.invoke(target, args)发起调用。JVM 作为中介,负责查找真实的元数据、进行访问权限检查、参数类型转换匹配、栈帧构建等复杂协调工作,最终将调用请求分发给目标对象的方法执行。
3. java.awt.Component 事件分发机制
在 AWT 或 Swing 中,Container(如 JFrame、JPanel)充当了事件分发的核心中介者。
- Mediator:
Container内部的EventQueue和事件分发线程EventDispatchThread。 - Colleague:
JButton、JTextField等子组件。 - 交互逻辑:当一个按钮被点击时,底层操作系统事件被封装为
AWTEvent放入EventQueue。Container作为中介,不断从队列中取出事件,并查找当前鼠标坐标下最深层的Component,然后调用该组件的dispatchEvent方法。组件本身不需要知道顶层窗口的结构,它们只需注册ActionListener并等待中介者通知即可。
3.2 Spring框架深度剖析
1. ApplicationContext(IoC容器)
Spring 的 ApplicationContext 是整个框架最核心的中介者。在传统 Java 开发中,对象 A 需要使用对象 B,必须主动 new B() 或通过工厂查找,形成直接依赖。而在 Spring 中:
- Mediator:
ApplicationContext(具体实现如ClassPathXmlApplicationContext)。 - Colleague: 各种被
@Component注解的 Bean。 - 交互逻辑:Bean 之间不再直接查找对方,而是声明依赖(
@Autowired)或通过实现ApplicationContextAware接口持有对容器的引用。当 Bean A 需要调用 Bean B 的方法时,容器(中介者)已经提前完成了依赖注入和 AOP 代理增强,A 直接调用注入的 B 实例即可。容器在后台协调了 Bean 的生命周期、作用域管理、循环依赖解决等复杂的交互逻辑。
2. DispatcherServlet(Spring MVC核心)
这是中介者模式在 Web 层的教科书级实现。
- Mediator:
DispatcherServlet。 - Colleague:
HandlerMapping,HandlerAdapter,ViewResolver,HandlerInterceptor等策略组件。 - 交互流程:
- 请求到达
DispatcherServlet。 - 中介者调用
HandlerMapping获取处理器执行链。 - 中介者调用
HandlerAdapter执行具体的 Controller 方法。 - 中介者获取返回的
ModelAndView,调用ViewResolver解析视图。 - 中介者渲染视图并返回响应。
在整个过程中,Controller 不知道是哪个
ViewResolver在处理,HandlerMapping也不知道后续有没有拦截器。所有的协作流程完全由DispatcherServlet这一前端控制器(Front Controller,本质就是中介者)集中编排。
- 请求到达
3. ApplicationEventMulticaster
Spring 的事件机制是对观察者模式的实现,而 ApplicationEventMulticaster 则是事件广播的中介者。
- Mediator:
SimpleApplicationEventMulticaster(默认实现)。 - Colleague:
ApplicationListener实现类。 - 交互逻辑:当业务代码调用
context.publishEvent(event)时,该调用被委托给ApplicationEventMulticaster。中介者内部维护了所有监听器的列表,它负责遍历监听器,并根据同步或异步策略调用监听器的onApplicationEvent方法。监听器之间互不依赖,它们只需向容器注册即可。
3.3 MyBatis框架
1. MapperRegistry 与 MapperProxyFactory
MyBatis 中,我们只定义接口(如 UserMapper),并不提供实现类,但运行时却能直接注入并调用。这里 MapperRegistry 和 MapperProxyFactory 扮演了中介者角色。
- Mediator:
Configuration类中的MapperRegistry。 - Colleague: 被
@MapperScan扫描的 Mapper 接口 与SqlSession。 - 交互逻辑:启动时,
MapperRegistry扫描所有 Mapper 接口,并为每个接口关联一个MapperProxyFactory。当业务代码调用userMapper.selectById(id)时,实际上是调用了 JDK 动态代理生成的MapperProxy对象。MapperProxy拦截调用,根据接口全限定名和方法名,从Configuration(中介者)中查找对应的MappedStatement(SQL 映射信息),最终委托SqlSession执行 SQL。接口与 SQL 执行逻辑完全解耦,Configuration作为全局配置中心,协调了接口、XML 映射文件和 JDBC 执行器之间的复杂关系。
3.4 Netty框架
1. EventLoopGroup 与 ChannelPipeline
Netty 的线程模型和数据处理管道充满了中介者的智慧。
- Mediator:
EventLoopGroup(具体为NioEventLoopGroup)。 - Colleague:
Channel(代表连接)。 - 交互逻辑:当一个
ServerSocketChannel接收到新连接时,它会将该连接注册到一个EventLoop上。EventLoopGroup作为中介者,负责负载均衡,将成千上万个Channel分配给有限数量的EventLoop线程管理。Channel本身不关心自己被分配给了哪个线程,它只需向EventLoop提交任务。此外,ChannelPipeline内部的ChannelHandlerContext也是一个微型中介者,它协调了ChannelInboundHandler和ChannelOutboundHandler之间的流转。
3.5 Dubbo框架
1. RegistryDirectory 与 RegistryProtocol
在 Dubbo 服务发现机制中,注册中心是天然的中介者,而客户端侧的 RegistryDirectory 则是该中介者思想的本地代理。
- Mediator: Zookeeper/Nacos 注册中心 + 客户端侧的
RegistryDirectory。 - Colleague: 服务消费者(Consumer) 与 服务提供者(Provider)。
- 交互逻辑:服务消费者并不直接配置提供者的 IP 地址,而是向注册中心订阅服务接口。
RegistryDirectory作为消费者端的中介者,监听注册中心的数据变化,动态维护本地的提供者列表。当发起 RPC 调用时,Cluster层结合LoadBalance策略从RegistryDirectory维护的列表中选出一个提供者地址。消费者与提供者位置完全解耦,提供者上下线对消费者透明,这一切都由注册中心和本地目录服务这个中介组合来协调。
四、分布式环境下的中介者模式
当系统演进到分布式架构,中介者模式不仅没有过时,反而以更宏大、更基础的形态支撑着整个分布式协作体系。
4.1 消息中间件作为分布式中介者
RabbitMQ 的 Exchange、Kafka 的 Topic 是典型的中介者模式在基础设施层的体现。
- 中介者角色:Broker 服务器集群。
- 同事角色:生产者(Producer)和消费者(Consumer)。
- 协作机制:生产者不再直接向消费者发送 TCP 请求,而是将消息发送给 Broker 的特定 Exchange/Topic。Broker 作为中介,根据预设的路由规则(Routing Key、Partition 策略),将消息复制、持久化,并推送给订阅了该队列的消费者。这使得生产者无需关心消费者是否在线、性能如何,消费者也无需关心生产者是谁。这是异步解耦的最高境界。
4.2 服务注册中心的中介者角色
Eureka、Nacos、Consul 等注册中心是微服务架构的通信中枢。
- 中介者角色:注册中心服务端。
- 同事角色:微服务提供者(向中心注册)和消费者(从中心订阅)。
- 协作机制:在调用链中,服务 A 调用服务 B 看似是直连(通过 Feign Client),但直连的前提是服务 A 必须通过中介者(注册中心)找到服务 B 的地址列表。注册中心不仅提供静态地址簿,还通过心跳机制动态剔除不健康的节点,这一协调动作对于 A 和 B 是透明的。
4.3 分布式协调服务的深度应用
ZooKeeper/etcd 是实现分布式锁、配置中心的核心中介者。
- 中介者角色:ZooKeeper 集群。
- 同事角色:争抢锁的多个服务节点。
- 协作机制:节点 A 和节点 B 都想获取同一把锁。它们不互相通信,而是都向 ZooKeeper 的一个特定节点(如
/lock/resource_01)尝试创建临时顺序节点。ZooKeeper 作为中介者,维护了节点的唯一性和顺序性,并通知各个客户端当前是否获得锁。分布式锁的逻辑(如避免惊群效应、锁释放通知)完全由 ZK 中介者封装,客户端只需遵循简单的 API 调用。
4.4 API网关的中介者作用
Spring Cloud Gateway、Kong 是前端与后端服务的中介者。
- 中介者角色:网关服务。
- 同事角色:浏览器/App 与 后端微服务集群。
- 协作机制:浏览器不需要知道后台有几百个微服务,它只需要知道网关地址。网关作为唯一入口,负责路由转发(根据 URL 找服务)、安全认证、流量控制。如果没有网关中介,前端代码需要硬编码所有微服务的域名,并自行处理跨域和安全问题,耦合极高。
4.5 微服务编排引擎
Netflix Conductor、Camunda 是典型的服务编排中介者。
- 中介者角色:编排引擎(Orchestrator)。
- 同事角色:各个微服务任务节点(Workers)。
- 协作机制:以一个下单流程为例:下单 -> 扣库存 -> 支付 -> 发货。在编排模式(Orchestration)下,Order Service 不需要调用 Inventory Service,而是由编排引擎作为总指挥,依次调用各个 Worker。引擎维护了流程的状态机,决定了下一步做什么、失败了如何补偿。这正是中介者模式在业务流程级别的应用。
4.6 分布式中介者架构图与Redis Pub/Sub示例
Redis Pub/Sub 分布式聊天室 Demo
// 依赖 Jedis: redis.clients:jedis
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
// 1. Redis 充当分布式中介者 (无需定义类,直接使用 Redis Server)
// 2. 同事类:订阅者
class Subscriber extends JedisPubSub {
private String name;
public Subscriber(String name) { this.name = name; }
@Override
public void onMessage(String channel, String message) {
// 收到中介者(Redis)转发的消息
System.out.println("[" + name + "] 收到来自频道 " + channel + " 的消息: " + message);
}
}
// 3. 同事类:发布者 (直接通过客户端发送给Redis)
class Publisher {
private Jedis jedis;
public Publisher(Jedis jedis) { this.jedis = jedis; }
public void publish(String channel, String message) {
jedis.publish(channel, message);
}
}
public class RedisMediatorDemo {
public static void main(String[] args) throws InterruptedException {
// 连接中介者 (Redis)
Jedis jedis1 = new Jedis("localhost", 6379);
Jedis jedis2 = new Jedis("localhost", 6379);
Subscriber alice = new Subscriber("Alice");
Subscriber bob = new Subscriber("Bob");
// 模拟异步订阅
new Thread(() -> jedis1.subscribe(alice, "chat-room")).start();
new Thread(() -> jedis2.subscribe(bob, "chat-room")).start();
Thread.sleep(1000);
Publisher charlie = new Publisher(new Jedis("localhost", 6379));
charlie.publish("chat-room", "Hello Redis Mediator!");
}
}
分布式中介者架构图(Mermaid Flowchart)
flowchart LR
subgraph 生产者/消费者组
ServiceA[微服务 A<br/>发布者/订阅者]
ServiceB[微服务 B<br/>订阅者]
ServiceC[微服务 C<br/>发布者]
end
subgraph 中介者层
Registry[服务注册中心<br/>Nacos / Eureka]
MQ[消息中间件<br/>Kafka / RabbitMQ]
Cache[分布式缓存/协调器<br/>Redis Pub/Sub / ZooKeeper]
end
ServiceA -- 1. 注册/订阅 --> Registry
ServiceB -- 1. 注册/订阅 --> Registry
ServiceC -- 2. 发布消息 --> MQ
MQ -- 3. 推送消息 --> ServiceA
MQ -- 3. 推送消息 --> ServiceB
ServiceA -- 4. 读写锁/配置 --> Cache
4.7 分布式中介者架构图文字说明
上图展示了分布式环境下中介者模式的层次化应用,揭示了服务间通信如何通过中间件解耦。
-
服务注册中心(左侧):微服务 A、B 作为同事对象,在启动时向
Registry中介者报告自己的位置和状态。当服务 A 需要调用服务 B 时,它只需询问中介者Registry获取 B 的可用地址列表。这构成了服务发现层面的星型拓扑。没有注册中心,A 必须硬编码 B 的地址,导致运维灾难。 -
消息中间件(中间):对于异步业务场景,服务 C(发布者)将业务事件发送给
MQ中介者。服务 C 不关心到底谁消费了这条消息。MQ作为中介者,负责消息的持久化存储、流量削峰填谷以及根据订阅关系将消息路由给服务 A 和服务 B。这种模式下,生产者与消费者的生命周期完全解耦,即使消费者宕机,消息也不会丢失。 -
分布式协调器(右侧):Redis Pub/Sub 或 ZooKeeper 提供了更底层的协调能力。例如,多个服务实例想要选出一个 Leader 执行定时任务。它们不直接互相投票,而是向 ZooKeeper 的某个临时节点发起创建请求。ZooKeeper 集群作为中立、高可用的中介者,裁决谁创建成功(成为 Leader),并通知其他实例监听该节点变化。这避免了服务实例之间复杂的网络交互和状态同步逻辑。
综上所述,在分布式系统中,中介者模式已从代码级别的设计模式,演变为架构级别的顶层设计原则。各类中间件本质上就是专用的大型中介者对象。
五、对比辨析
| 对比维度 | 中介者模式 | 观察者模式 | 门面模式 | 代理模式 |
|---|---|---|---|---|
| 核心意图 | 协调多个对象之间的复杂交互和依赖 | 定义对象间的一对多依赖,当对象状态改变时自动通知 | 为子系统中的一组接口提供一个一致的界面 | 控制对单一对象的访问,增加间接层 |
| 耦合方向 | 多对一(Colleague 依赖 Mediator) | 一对多(Subject 通知 Observer) | 多对一(Client 依赖 Facade) | 一对一(Proxy 对应 Subject) |
| 通信流向 | 双向或多向路由。中介者接收并分发消息。 | 单向广播。主题状态改变,通知观察者。 | 单向请求。客户端请求门面,门面转发给子系统。 | 单向/双向控制。代理拦截请求。 |
| 典型场景 | GUI 组件联动、聊天室、航空管制、微服务编排 | 事件监听、发布-订阅、Model-View 更新 | 支付网关封装、复杂库的简化 API | 延迟加载、权限校验、远程代理 |
5.1 中介者模式与事件总线
事件总线(Event Bus)可以看作是中介者模式的一种轻量化、通用化实现,但两者在复杂度和应用边界上存在显著差异。
- 中介者模式:通常是为特定领域(如聊天室、对话框)定制的。中介者类内部包含了具体的业务协调逻辑,知道如何根据
ColleagueA的状态去改变ColleagueB。它是有状态的、业务相关的。 - 事件总线:通常是通用基础设施(如 Guava EventBus, Spring ApplicationEvent)。它只负责事件的派发,不包含业务逻辑。事件总线本身不知道
OrderCreatedEvent发生后应该调用哪个服务,它只管把事件扔给所有订阅了该事件类型的监听器。
设计复杂度与适用边界:
- 当对象间的交互逻辑非常复杂且易变(如需要根据上下文状态决定通知谁、如何校验),应当使用中介者模式,将逻辑集中管理,避免散布在多个监听器中难以追踪。
- 当仅仅是为了解耦模块间的直接引用,且交互逻辑是简单的广播通知,应当使用事件总线,以更低的侵入成本实现松耦合。
六、适用场景分析(重点强化)
针对每一个典型场景,本节将提供独立、完整且可直接运行的 Demo 代码,并配合详尽的 Mermaid 图表与文字解析。
场景一:多人聊天室系统
1. Mermaid 类图
classDiagram
class ChatRoom {
<<interface>>
+showMessage(user: User, message: String, type: MessageType)
+addUser(user: User)
}
class ChatRoomImpl {
-users: Map
+showMessage(user: User, message: String, type: MessageType)
+addUser(user: User)
-sendPrivateMessage(user: User, message: String)
-sendBroadcastMessage(message: String)
}
class User {
<<abstract>>
#chatRoom: ChatRoom
#name: String
+User(chatRoom: ChatRoom, name: String)
+send(message: String, receiverName: String)
+receive(message: String, senderName: String)
}
class ChatUser {
+send(message: String, receiverName: String)
+receive(message: String, senderName: String)
}
ChatRoom <|.. ChatRoomImpl
User <|-- ChatUser
ChatRoomImpl o-- User : contains
ChatUser --> ChatRoom : uses
2. 完整可运行 Demo
import java.util.HashMap;
import java.util.Map;
enum MessageType { PRIVATE, BROADCAST }
interface ChatRoom {
void showMessage(User user, String message, String receiverName);
void addUser(User user);
}
class ChatRoomImpl implements ChatRoom {
private Map<String, User> users = new HashMap<>();
@Override
public void addUser(User user) {
users.put(user.getName(), user);
System.out.println("系统: " + user.getName() + " 加入了聊天室");
}
@Override
public void showMessage(User sender, String message, String receiverName) {
if (receiverName == null) {
System.out.println("\n--- [广播] " + sender.getName() + " 对所有人说: " + message + " ---");
for (User user : users.values()) {
if (!user.getName().equals(sender.getName())) {
user.receive(message, sender.getName());
}
}
} else {
User receiver = users.get(receiverName);
if (receiver != null) {
System.out.println("\n--- [私聊] " + sender.getName() + " 对 " + receiverName + " 说: " + message + " ---");
receiver.receive(message, sender.getName());
}
}
}
}
abstract class User {
protected ChatRoom chatRoom;
protected String name;
public User(ChatRoom chatRoom, String name) {
this.chatRoom = chatRoom;
this.name = name;
}
public String getName() { return name; }
public abstract void send(String message, String receiverName);
public abstract void receive(String message, String senderName);
}
class ChatUser extends User {
public ChatUser(ChatRoom chatRoom, String name) { super(chatRoom, name); }
@Override
public void send(String message, String receiverName) {
chatRoom.showMessage(this, message, receiverName);
}
@Override
public void receive(String message, String senderName) {
System.out.println("[" + name + " 的聊天框] " + senderName + ": " + message);
}
}
public class ChatRoomDemo {
public static void main(String[] args) {
ChatRoom room = new ChatRoomImpl();
User alice = new ChatUser(room, "Alice");
User bob = new ChatUser(room, "Bob");
User charlie = new ChatUser(room, "Charlie");
room.addUser(alice);
room.addUser(bob);
room.addUser(charlie);
alice.send("Hi everyone!", null);
bob.send("Hello Alice, can we talk privately?", "Alice");
charlie.send("I can't hear the private talk!", null);
}
}
3. 文字说明
该场景展示了中介者模式最直观的应用:将网状社交关系重构为星型拓扑。
核心机制分析:
- 避免网状引用:在上述代码中,
ChatUser类(具体同事类)的源码里,完全没有出现List<User> friends这样的字段。每一个User对象只知道两件事:自己的名字,以及聊天室中介者ChatRoom。当 Alice 想要通信时,她不再需要遍历一个可能包含数百人的好友列表,而是直接把消息扔给chatRoom.showMessage()。 - 策略集中化:消息过滤与广播策略完全封装在
ChatRoomImpl.showMessage()方法中。这带来了巨大的灵活性。例如,我们想要加入“敏感词过滤”功能,只需在showMessage方法开头添加一行过滤代码即可,所有用户瞬间生效,无需修改任何User类的代码。同样,如果想要实现“黑名单”(例如 Bob 屏蔽了 Charlie),也只需在showMessage的私聊分支中增加一个基于sender和receiver的判断逻辑。 - 状态隔离:User 对象是无状态的(关于其他 User 的状态),所有全局状态(比如当前在线用户列表
users)都归属中介者管理。这种职责的单一性使得单元测试变得异常简单——我们可以轻松 Mock 一个ChatRoom来测试User的行为。
场景二:GUI对话框组件协调
1. Mermaid 流程图
flowchart TD
subgraph GUI组件层
A[用户名输入框<br/>触发输入事件] --> M
B[密码框<br/>触发输入事件] --> M
C[确认密码框<br/>触发输入事件] --> M
D[注册按钮<br/>初始禁用]
end
subgraph 中介者层
M[DialogMediator<br/>协调中心]
end
M -- 1. 获取输入值 --> A
M -- 2. 获取输入值 --> B
M -- 3. 获取输入值 --> C
M -- 4. 执行校验逻辑 --> M
M -- 5. 校验通过 -> 设置启用 --> D
M -- 5. 校验失败 -> 设置禁用 --> D
2. 完整可运行 Demo
import java.util.ArrayList;
import java.util.List;
// 抽象同事类:GUI组件
abstract class Widget {
protected DialogMediator mediator;
public Widget(DialogMediator mediator) { this.mediator = mediator; }
public abstract void changed();
}
// 具体同事:文本输入框
class TextBox extends Widget {
private String value = "";
public TextBox(DialogMediator mediator) { super(mediator); }
public void setValue(String value) {
this.value = value;
System.out.println("输入框内容变更: " + value);
changed(); // 通知中介者
}
public String getValue() { return value; }
@Override public void changed() { mediator.widgetChanged(this); }
}
// 具体同事:按钮
class Button extends Widget {
private boolean enabled = false;
private String caption;
public Button(DialogMediator mediator, String caption) {
super(mediator);
this.caption = caption;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
System.out.println("按钮 [" + caption + "] 状态变更为: " + (enabled ? "可用" : "禁用"));
}
@Override public void changed() { mediator.widgetChanged(this); }
}
// 抽象中介者
interface DialogMediator {
void widgetChanged(Widget widget);
}
// 具体中介者:注册对话框协调器
class RegisterDialogMediator implements DialogMediator {
private TextBox usernameBox;
private TextBox passwordBox;
private TextBox confirmBox;
private Button registerButton;
private List<Widget> widgets = new ArrayList<>();
public void setUsernameBox(TextBox usernameBox) { this.usernameBox = usernameBox; widgets.add(usernameBox); }
public void setPasswordBox(TextBox passwordBox) { this.passwordBox = passwordBox; widgets.add(passwordBox); }
public void setConfirmBox(TextBox confirmBox) { this.confirmBox = confirmBox; widgets.add(confirmBox); }
public void setRegisterButton(Button registerButton) { this.registerButton = registerButton; widgets.add(registerButton); }
@Override
public void widgetChanged(Widget widget) {
System.out.println("中介者: 收到组件变更通知,开始校验...");
// 核心协调逻辑:只有用户名非空且密码一致时,按钮才可用
boolean isUsernameValid = usernameBox.getValue() != null && !usernameBox.getValue().isEmpty();
boolean isPasswordMatch = passwordBox.getValue().equals(confirmBox.getValue()) && !passwordBox.getValue().isEmpty();
if (isUsernameValid && isPasswordMatch) {
registerButton.setEnabled(true);
} else {
registerButton.setEnabled(false);
}
}
}
public class GUIDemo {
public static void main(String[] args) {
RegisterDialogMediator mediator = new RegisterDialogMediator();
TextBox username = new TextBox(mediator);
TextBox password = new TextBox(mediator);
TextBox confirm = new TextBox(mediator);
Button registerBtn = new Button(mediator, "立即注册");
mediator.setUsernameBox(username);
mediator.setPasswordBox(password);
mediator.setConfirmBox(confirm);
mediator.setRegisterButton(registerBtn);
// 模拟用户操作
username.setValue("user1");
password.setValue("123");
confirm.setValue("456"); // 不一致 -> 按钮禁用
confirm.setValue("123"); // 改为一致 -> 按钮启用
}
}
3. 文字说明
在 GUI 开发中,组件联动逻辑往往是代码腐化的重灾区。如果不使用中介者模式,开发者通常会在每个 TextBox 的 DocumentListener 或 FocusListener 中编写如下代码:
// 反模式代码示例
passwordField.addActionListener(e -> {
if (!passwordField.getText().equals(confirmField.getText())) {
registerButton.setEnabled(false);
} else if (!usernameField.getText().isEmpty()) {
registerButton.setEnabled(true);
}
});
这种写法导致交互逻辑分散在数十个匿名内部类中,当需求变更(例如增加“手机号格式校验”或“同意协议勾选框”),开发者必须在茫茫代码海中寻找所有相关监听器并进行修改,极易遗漏。
中介者模式在此处的优势体现在:
- 逻辑集中化:所有关于“注册按钮何时可点”的逻辑,被完全封装在
RegisterDialogMediator.widgetChanged()这一个方法中。无论有多少个输入组件(用户名、密码、确认密码、验证码、手机号),它们的校验逻辑都集中在此。 - 组件无状态依赖:
TextBox类不需要知道Button类的存在,也不需要知道自己的输入会影响哪个组件的状态。它只需在内容改变时大喊一声“我变了!”(changed())。这种“通知”而非“调用”的机制,使得我们可以随时替换组件类型(例如把TextBox换成PasswordField)而不影响协调逻辑。 - 可测试性强:我们可以直接编写单元测试,模拟
widgetChanged的调用,传入不同的模拟Widget状态,断言Button的enabled状态是否正确,全程无需启动真实的 Swing 或 JavaFX 窗口。
场景三:航空交通管制系统
1. Mermaid 时序图
sequenceDiagram
participant A as Aircraft A<br/>(请求起飞)
participant ATC as AirTrafficControl<br/>(中介者/塔台)
participant B as Aircraft B<br/>(等待降落)
participant Runway as 跑道资源
A->>ATC: 1. 请求起飞(RequestTakeoff)
activate ATC
ATC->>ATC: 2. 检查跑道占用状态
Note right of ATC: 跑道当前空闲
ATC->>B: 3. 通知: 有飞机准备起飞, 请等待
B-->>ATC: 4. 确认收到(ACK)
ATC->>A: 5. 批准起飞(Clearance)
A->>Runway: 6. 进入跑道起飞
deactivate ATC
2. 完整可运行 Demo
import java.util.LinkedList;
import java.util.Queue;
// 同事接口
interface Aircraft {
void requestTakeoff();
void requestLanding();
void receive(String message);
String getFlightNumber();
}
// 具体同事
class ConcreteAircraft implements Aircraft {
private String flightNumber;
private AirTrafficControl atc;
public ConcreteAircraft(String flightNumber, AirTrafficControl atc) {
this.flightNumber = flightNumber;
this.atc = atc;
atc.registerAircraft(this);
}
@Override public void requestTakeoff() { atc.requestTakeoff(this); }
@Override public void requestLanding() { atc.requestLanding(this); }
@Override public void receive(String message) {
System.out.println("[" + flightNumber + "] 收到塔台指令: " + message);
}
@Override public String getFlightNumber() { return flightNumber; }
}
// 抽象中介者
interface AirTrafficControl {
void registerAircraft(Aircraft aircraft);
void requestTakeoff(Aircraft aircraft);
void requestLanding(Aircraft aircraft);
}
// 具体中介者:模拟单跑道管制
class ControlTower implements AirTrafficControl {
private boolean runwayOccupied = false;
private Queue<Aircraft> waitingQueue = new LinkedList<>();
@Override
public void registerAircraft(Aircraft aircraft) {
System.out.println("塔台: " + aircraft.getFlightNumber() + " 已进入管制空域");
}
@Override
public synchronized void requestTakeoff(Aircraft aircraft) {
System.out.println("塔台: 收到 " + aircraft.getFlightNumber() + " 起飞请求");
if (runwayOccupied) {
aircraft.receive("跑道被占用,请在等待点排队等待");
waitingQueue.add(aircraft);
} else {
runwayOccupied = true;
aircraft.receive("跑道空闲,允许起飞,风向 360,风速 5 节");
// 模拟起飞过程,完成后释放跑道
new Thread(() -> {
try { Thread.sleep(3000); } catch (InterruptedException e) {}
System.out.println("塔台: " + aircraft.getFlightNumber() + " 已离地,跑道空闲");
runwayOccupied = false;
processNext();
}).start();
}
}
@Override
public synchronized void requestLanding(Aircraft aircraft) {
// 降落优先级通常高于起飞,此处简化为排队逻辑
System.out.println("塔台: 收到 " + aircraft.getFlightNumber() + " 降落请求");
if (runwayOccupied) {
aircraft.receive("跑道占用,请盘旋等待或复飞");
waitingQueue.add(aircraft);
} else {
runwayOccupied = true;
aircraft.receive("可以降落,跑道 27L");
new Thread(() -> {
try { Thread.sleep(4000); } catch (InterruptedException e) {}
System.out.println("塔台: " + aircraft.getFlightNumber() + " 已脱离跑道");
runwayOccupied = false;
processNext();
}).start();
}
}
private void processNext() {
if (!waitingQueue.isEmpty()) {
Aircraft next = waitingQueue.poll();
if (next instanceof ConcreteAircraft) {
// 简化处理,实际需区分起降
requestTakeoff(next);
}
}
}
}
public class ATCDemo {
public static void main(String[] args) throws InterruptedException {
ControlTower tower = new ControlTower();
Aircraft flight123 = new ConcreteAircraft("CA123", tower);
Aircraft flight456 = new ConcreteAircraft("MU456", tower);
flight123.requestTakeoff();
Thread.sleep(500);
flight456.requestTakeoff();
Thread.sleep(10000); // 等待异步操作完成
}
}
3. 文字说明
航空交通管制是中介者模式在生命财产安全关键系统中的经典隐喻。
全局状态维护:
在这个模拟程序中,ControlTower(中介者)维护了关键的全局状态:runwayOccupied(跑道占用标志)和 waitingQueue(等待队列)。如果让飞机对象 Aircraft 之间互相通信来协调跑道使用,飞机 A 必须先问飞机 B:“你用跑道吗?”,然后问飞机 C:“你排第几?”。这种点对点协商在网络不稳定或信息不对称时极其容易导致碰撞事故(逻辑冲突)。
安全调度策略封装:
中介者 ControlTower 将安全调度策略(先到先得、降落优先)封装在 requestTakeoff 和 requestLanding 的 synchronized 方法中。这确保了在任何时刻,只有一个决策点来决定跑道的分配。飞机作为同事类,职责被极度简化:它们只需在进入空域时报到(registerAircraft),并在需要时提出申请。它们完全信任中介者的裁决,即使收到“等待”指令,也只需执行指令,无需了解原因(因为塔台知道全局视图)。
时序流程解读: 时序图清晰地展示了请求-响应模式:飞机 A 发起请求(步骤1),中介者立即进行状态检查(步骤2),发现跑道空闲,但它仍向飞机 B 发出通知(步骤3),告知其改变状态(如监听频道)。这体现了中介者不仅响应直接请求者,还会主动通知受影响的第三方(观察者模式的结合)。最终,中介者才给 A 下达最终指令(步骤5)。这个过程模拟了真实塔台的工作流:先评估影响,再协调周边,最后下达许可。
场景四:微服务间订单协调(服务编排)
1. Mermaid 时序图(服务编排模式)
sequenceDiagram
participant Client as 客户端
participant Mediator as 订单中介者<br/>(OrderOrchestrator)
participant Inventory as 库存服务
participant Payment as 支付服务
participant Logistics as 物流服务
Client->>Mediator: 1. 提交订单(商品A)
activate Mediator
Mediator->>Inventory: 2. 扣减库存(商品A, 1)
activate Inventory
Inventory-->>Mediator: 库存扣减成功
deactivate Inventory
Mediator->>Payment: 3. 创建支付单
activate Payment
Payment-->>Mediator: 支付单已创建,待支付
deactivate Payment
Mediator->>Logistics: 4. 创建发货单
activate Logistics
Logistics-->>Mediator: 发货单已创建
deactivate Logistics
Mediator-->>Client: 5. 订单预下单成功
deactivate Mediator
Note over Client,Mediator: 后续异步流程:用户支付 -> 支付回调通知中介者 -> 中介者调用物流发货
2. 完整可运行 Demo(模拟同步服务调用)
// 模拟各个微服务
class InventoryService {
public boolean deduct(String productId, int quantity) {
System.out.println("[库存服务] 扣减商品 " + productId + " 库存: " + quantity);
return true;
}
}
class PaymentService {
public String createTransaction(String orderId, double amount) {
System.out.println("[支付服务] 创建交易流水,订单: " + orderId);
return "TXN_" + System.currentTimeMillis();
}
}
class LogisticsService {
public void scheduleDelivery(String orderId, String address) {
System.out.println("[物流服务] 安排发货,订单: " + orderId);
}
}
// 中介者:订单编排器
class OrderOrchestrator {
private InventoryService inventory = new InventoryService();
private PaymentService payment = new PaymentService();
private LogisticsService logistics = new LogisticsService();
// 同事类(外部调用者)通过此方法发起协作
public OrderResult placeOrder(OrderRequest request) {
System.out.println("中介者: 开始处理订单 " + request.orderId);
// 1. 协调库存
boolean stockOk = inventory.deduct(request.productId, request.quantity);
if (!stockOk) {
return new OrderResult(false, "库存不足");
}
// 2. 协调支付
String txnId = payment.createTransaction(request.orderId, request.amount);
// 3. 协调物流
logistics.scheduleDelivery(request.orderId, request.address);
System.out.println("中介者: 订单 " + request.orderId + " 处理完成");
return new OrderResult(true, "订单创建成功, 交易号: " + txnId);
}
}
// 辅助数据类
class OrderRequest {
String orderId; String productId; int quantity; double amount; String address;
public OrderRequest(String oid, String pid, int qty, double amt, String addr) {
this.orderId = oid; this.productId = pid; this.quantity = qty; this.amount = amt; this.address = addr;
}
}
class OrderResult {
boolean success; String message;
public OrderResult(boolean s, String m) { this.success = s; this.message = m; }
}
public class MicroserviceDemo {
public static void main(String[] args) {
OrderOrchestrator orchestrator = new OrderOrchestrator();
OrderRequest req = new OrderRequest("ORD-1001", "SKU-123", 2, 199.0, "北京市朝阳区");
OrderResult result = orchestrator.placeOrder(req);
System.out.println("客户端收到结果: " + result.message);
}
}
3. 文字说明
在微服务架构中,实现复杂的业务流程通常有两条路径:编排(Orchestration) 和 编制(Choreography)。
- 编排(中介者模式):如上文 Demo 所示,
OrderOrchestrator作为一个独立的“大脑”,负责指挥库存、支付、物流等服务完成工作流。这是一种命令式的协调方式。它的优点是流程清晰可见:打开placeOrder方法,整个下单流程一目了然,便于监控和排错。缺点是中介者可能成为开发瓶颈(团队都在改同一个服务)和性能瓶颈(单点故障)。 - 编制(非中介者模式/观察者模式变体):这是一种反应式的协调方式。没有中心大脑。库存服务扣减成功后,发布
InventoryDeductedEvent;支付服务监听到该事件后,自行处理支付并发布PaymentProcessedEvent;物流服务再监听支付事件进行发货。各服务通过事件总线松耦合。优点是高度自治和弹性,缺点是业务流程散落在多个服务的代码和配置中,难以洞察全局业务状态,调试极其困难。
中介者模式对应服务编排的优劣分析:
本 Demo 体现了中介者模式在服务编排中的核心价值——流程的显式化。对于核心交易链路(如下单、退款),业务的正确性和一致性至关重要。使用编排模式(中介者),我们可以利用 Saga 模式在 OrderOrchestrator 中集中编写补偿逻辑(如支付失败则回滚库存),逻辑内聚性极强。相比之下,编制模式虽然更解耦,但在处理分布式事务补偿时,往往需要依赖复杂的 BPMN 工作流引擎或隐式的回调链,维护成本较高。
场景五:多人协作在线文档
1. Mermaid 流程图(操作转换 OT 简化流程)
flowchart TD
subgraph "客户端A"
A1["输入字符 'H'"] --> A2["发送操作: {insert: 'H', pos: 0}"]
end
A2 -->|"WebSocket"| M["DocumentMediator 状态维护与冲突检测"]
subgraph "中介者处理"
M --> M1{"检查版本号"}
M1 -- "版本匹配" --> M2["应用操作到主文档"]
M2 --> M3["广播操作给其他客户端"]
M1 -- "版本冲突" --> M4["执行 OT 转换算法"]
M4 --> M5["转换操作并应用"]
M5 --> M3
end
M3 -->|"WebSocket"| B["客户端B"]
M3 -->|"WebSocket"| C["客户端C"]
2. 完整可运行 Demo(模拟简单协同编辑)
import java.util.*;
// 操作对象
class EditOperation {
String userId;
String type; // "insert", "delete"
String data;
int position;
int version; // 基于的文档版本
public EditOperation(String uid, String type, String data, int pos, int ver) {
this.userId = uid; this.type = type; this.data = data; this.position = pos; this.version = ver;
}
}
// 中介者:文档协作服务器
class DocumentMediator {
private StringBuilder document = new StringBuilder(""); // 权威文档状态
private int currentVersion = 0;
private List<EditorClient> clients = new ArrayList<>();
public void register(EditorClient client) {
clients.add(client);
System.out.println("中介者: 用户 " + client.getUserId() + " 进入文档");
// 同步初始状态给新客户端
client.receiveSync(document.toString(), currentVersion);
}
public synchronized void applyEdit(EditOperation op, EditorClient sender) {
System.out.println("中介者: 收到 [" + sender.getUserId() + "] 的操作 (v" + op.version + ") : " + op.type + " '" + op.data + "' at " + op.position);
// 1. 冲突检测与操作转换 (简化处理,假设基于版本号的乐观锁)
if (op.version != currentVersion) {
System.out.println("中介者: 检测到冲突! 执行 OT 转换 (实际项目需实现具体OT/CRDT算法)");
// 转换逻辑示例:如果本地版本比op版本大,说明中间有操作插入,需要调整位置
// 此处为演示,直接返回错误或忽略
}
// 2. 应用操作到权威文档
if ("insert".equals(op.type)) {
document.insert(op.position, op.data);
}
// 3. 版本号递增
currentVersion++;
// 4. 广播给所有客户端(包括发送者确认)
EditOperation broadcastOp = new EditOperation(op.userId, op.type, op.data, op.position, currentVersion);
for (EditorClient client : clients) {
client.receiveBroadcast(broadcastOp);
}
}
}
// 同事类:编辑器客户端
class EditorClient {
private String userId;
private DocumentMediator mediator;
private StringBuilder localDoc = new StringBuilder("");
private int localVersion = 0;
public EditorClient(String userId, DocumentMediator mediator) {
this.userId = userId;
this.mediator = mediator;
mediator.register(this);
}
// 用户输入触发的操作
public void type(String text, int position) {
System.out.println("\n[" + userId + " 本地操作] 在位置 " + position + " 输入 '" + text + "'");
EditOperation op = new EditOperation(userId, "insert", text, position, localVersion);
mediator.applyEdit(op, this);
}
// 收到初始同步
public void receiveSync(String doc, int version) {
this.localDoc = new StringBuilder(doc);
this.localVersion = version;
System.out.println("[" + userId + " 界面] 文档已同步: '" + localDoc + "' v" + version);
}
// 收到广播
public void receiveBroadcast(EditOperation op) {
if ("insert".equals(op.type)) {
localDoc.insert(op.position, op.data);
}
localVersion = op.version;
System.out.println("[" + userId + " 界面] 应用远程操作: 内容变为 '" + localDoc + "' v" + localVersion);
}
public String getUserId() { return userId; }
}
public class CollabDemo {
public static void main(String[] args) {
DocumentMediator server = new DocumentMediator();
EditorClient alice = new EditorClient("Alice", server);
EditorClient bob = new EditorClient("Bob", server);
alice.type("Hello", 0);
bob.type(" World", 5);
alice.type("!", 11);
}
}
3. 文字说明(不少于200字)
在线文档协作(如 Google Docs、腾讯文档)是中介者模式与复杂算法(OT/CRDT)结合的巅峰应用。
中介者维护全局真理:
在上述 Demo 中,DocumentMediator 持有的 StringBuilder document 代表了文档的权威状态(Single Source of Truth)。所有客户端(Alice 和 Bob)看到的视图都是对该权威状态的投影。如果让客户端之间通过 P2P 方式直接同步,由于网络延迟和并发编辑,很快就会导致各客户端文档内容出现永久性分歧。中介者的存在强制规定了状态变更的顺序性:所有操作必须先经过服务器,服务器赋予操作一个唯一的、单调递增的版本号 currentVersion,然后才广播。
操作转换与冲突解决: 文字说明中提及的 OT(操作转换)算法,是中介者智能性的体现。当 Alice 基于版本 1 发送一个“在位置 5 插入 'A'”的操作时,如果网络较慢,服务器可能已经处理了 Bob 基于版本 1 的“在位置 3 删除一个字符”的操作,导致服务器版本变为 2。此时,Alice 的操作到达服务器,若直接执行,'A' 会被插入到错误的位置。中介者的核心价值在于它能执行 OT 算法:它能够根据 Bob 的操作(在位置 3 删除),将 Alice 的操作转换为“在位置 4 插入 'A'”,从而保证最终一致性。这种高度复杂的协调逻辑如果让客户端自行处理,不仅客户端代码会极其臃肿,而且极易出现安全漏洞(客户端不可信)。
与 WebSocket 的结合:
在实际系统中,DocumentMediator 通常作为 WebSocket 服务器。每个 EditorClient 对应一个 WebSocket 连接。中介者模式完美适配了 WebSocket 的全双工通信模型:客户端向服务器发送操作(applyEdit),服务器向多个客户端广播操作(receiveBroadcast)。
七、面试题精选与专家级解答
1. 中介者模式和观察者模式都可以实现对象间的解耦通信,它们的本质区别是什么?
答:虽然两者都旨在解耦,但意图和控制流完全不同。
- 观察者模式:侧重于一对多的状态广播。当 Subject 状态变化时,自动通知所有 Observer。Subject 并不知道 Observer 接下来要做什么,控制流是从 Subject 单向流向 Observer。它适合“通知”场景,例如邮件订阅、UI 更新。
- 中介者模式:侧重于多对多的交互协调。同事对象不仅接收消息,还主动发送消息并期望引发其他同事的特定行为。控制流是双向的、可路由的。它适合“协作”场景,例如 GUI 组件联动、飞机调度。
2. Spring MVC 中的 DispatcherServlet 是如何体现中介者模式的?请结合源码分析其工作流程。
答:DispatcherServlet 是中介者模式的完美实现。它充当了中央调度器。
- 同事对象:
HandlerMapping、HandlerAdapter、ViewResolver、HandlerExceptionResolver。 - 协调流程:
DispatcherServlet.doDispatch()接收请求。- 调用
getHandler(),内部委托给各个HandlerMapping组件(同事1)获取处理器链。 - 调用
getHandlerAdapter(),查找支持该 Handler 的HandlerAdapter(同事2)。 - 调用
ha.handle()执行 Controller,返回ModelAndView。 - 调用
processDispatchResult(),委托ViewResolver(同事3)解析视图。DispatcherServlet本身不处理具体业务,它只负责编排流程。源码中清晰的步骤化调用体现了“集中控制”的中介者思想。
3. 中介者模式可能会导致中介者对象过于臃肿,如何解决这个问题?
答:这是中介者模式最大的副作用。解决方案包括:
- 职责拆分:不要只用一个上帝类
GodMediator。按业务领域拆分为多个中介者,例如ChatMediator、AuthMediator。同事对象可以持有多个中介者的引用。 - 结合命令模式:将交互逻辑封装成
Command对象。中介者不再包含具体的 if-else 业务逻辑,而是根据命令类型将请求转发给对应的Command处理器执行。这使得中介者变成了一个简单的路由器。 - 引入事件总线:对于简单的通知类交互,从中介者中剥离出来,使用
EventBus替代,减轻主中介者负担。
4. 消息中间件(如 RabbitMQ)是如何体现中介者模式思想的?与经典中介者模式有何异同?
答:
- 相同点:都是通过引入第三方中间人(Broker/Mediator)将生产者与消费者解耦,实现星型拓扑通信。
- 不同点:
- 生命周期:经典中介者通常与同事对象存在于同一进程(JVM)内,生命周期一致。消息中间件是独立部署的分布式系统。
- 通信方式:经典中介者是同步方法调用;消息中间件是基于网络的异步消息传递。
- 可靠性:经典中介者宕机则系统崩溃;消息中间件通过集群、持久化提供高可用保障。
5. 在微服务架构中,服务编排(Orchestration)与编制(Choreography)分别对应什么设计模式?中介者模式更适合哪种?
答:
- 服务编排(Orchestration):对应中介者模式。存在一个中心编排器(如 Netflix Conductor)指挥各个服务执行。
- 服务编制(Choreography):对应观察者模式的分布式变体。无中心指挥,各服务通过监听事件做出反应。
- 适用性:对于强一致性、流程复杂、需要全局可视化的核心业务(如电商下单、保险理赔),中介者模式(编排)更合适,因为它集中了流程控制权,便于实施分布式事务补偿(Saga)。对于弱依赖、流程简单、注重吞吐量的辅助功能(如发送通知邮件、更新搜索索引),编制模式更合适,能避免编排器成为单点。
6. 如何用中介者模式重构一个包含大量互相引用的 GUI 组件系统?
答:
- 抽取接口:定义
DialogMediator接口,包含notify(Component sender, Event event)方法。 - 创建具体中介者:创建
ConcreteDialog类实现接口,并在构造函数中持有各个子组件(Button、List、TextField)的引用。 - 改造组件:让所有 GUI 子组件继承自统一的基类
BaseComponent,基类持有DialogMediator引用。移除子组件之间互相持有的字段。 - 事件转发:将原组件中
list.addListSelectionListener(e -> button.setEnabled(...))的代码替换为mediator.notify(this, "ITEM_SELECTED")。 - 逻辑集中:在
ConcreteDialog.notify方法中,根据发送者类型和事件类型编写统一的if-else或switch协调逻辑。
7. 中介者模式与门面模式在结构上有相似之处,如何从意图上区分二者?
答:
- 中介者模式:强调内部协作。它是子系统内部多个组件之间的交通警察。没有中介者,组件无法工作;有了中介者,组件新增了交互能力。
- 门面模式:强调外部简化。它是子系统对外部客户端提供的统一接待员。没有门面,客户端也能直接调用子系统(只是麻烦);有了门面,客户端调用变简单了,但子系统内部组件的交互方式并未改变。
8. 分布式系统中的服务注册中心(如 Nacos)体现了哪些设计模式?中介者模式在其中扮演什么角色?
答:
- 中介者模式:Nacos 是服务 A 和服务 B 的通信中介。A 不需要知道 B 的 IP,只需问 Nacos。
- 观察者模式:客户端向 Nacos 订阅服务,当服务列表变化时,Nacos 主动推送通知。
- 注册表模式(Registry Pattern):提供服务的登记与查找功能。
- 中介者角色:Nacos 作为中介者,最核心的贡献在于去中心化地址管理。它将原本需要硬编码或手工维护的地址关系,转变为动态的、基于服务名的查询关系,这是微服务体系松耦合的基石。
9. 中介者模式中的同事对象是否应该持有中介者的引用?是否可以通过事件机制替代直接引用?
答:
- 应该持有引用:经典 GoF 实现中,同事对象必须持有中介者的引用,以便调用
mediator.notify()。这是主动发起协作的唯一通道。 - 事件机制替代:可以替代,但这其实演变为了观察者模式或事件总线。如果同事对象不持有中介者引用,而是通过发布全局事件(如
EventBus.post(event))来发起通信,那么“中介者”变成了“事件总线订阅者”。虽然底层思想相通,但在面向对象设计语义上,持有显式引用更清晰地表达了“该同事类需要参与特定领域的协作”这一设计意图。
10. 如何设计一个支持动态扩展的中介者?请简述插件化中介者架构的实现思路。
答:设计一个插件化中介者的核心在于策略模式与责任链模式的结合。
- 定义处理器接口:定义
MediatorPlugin接口,包含boolean supports(Event event)和void process(Event event, Context ctx)。 - 中介者注册机制:中介者内部维护一个
List<MediatorPlugin>。提供registerPlugin(MediatorPlugin plugin)方法。 - 分发逻辑:当
notify被调用时,遍历插件列表,找到第一个supports返回true的插件并调用其process方法。 - 应用场景:例如在 IM 系统中,文本消息处理、图片消息处理、红包消息处理可以作为不同的插件动态加载。当需要新增“位置共享”功能时,只需开发一个新的
LocationSharePlugin,打包进 ClassPath 或通过 SPI 机制加载,中介者核心代码零改动。
八、结语
中介者模式是一把双刃剑。一方面,它通过引入中心节点,极其有效地化解了复杂系统对象间的网状依赖,使得逻辑内聚、系统松耦合;另一方面,如果设计不当,它极易导致“中介者臃肿症”,让中介者成为难以维护的“上帝类”。
作为专家级开发者,我们应当在系统设计初期识别出那些复杂、易变、多对象参与的交互场景,果断引入中介者模式进行重构。同时,要时刻警惕中介者的边界,适时结合观察者模式、命令模式、事件总线等手段为其减负。从 JDK 的 Timer 到 Spring 的 DispatcherServlet,再到分布式架构中的 Kafka 与 Nacos,中介者模式的思想贯穿了软件架构的始终。掌握了它,便掌握了解耦复杂系统的核心心法。