跳到主要内容

中介者模式-Mediator Pattern

序言​

想象一个没有塔台的机场:每一架飞机都需要直接与其他所有飞机通信,告诉对方自己的位置和航向。飞机越多,通信关系越复杂,任何一架飞机加入或离开,所有其他飞机都要重新建立联系——这种"多对多"的直接通信几乎无法维护。

航空业给出的答案就是塔台:所有飞机只和塔台通信,塔台统一调度、协调各飞机之间的飞行路线。飞机之间互不直接联系,但整个机场依然秩序井然。

中介者模式(Mediator Pattern)正是这种思想的软件实现:用一个中介对象封装一系列对象之间的交互,让对象之间不再直接引用彼此,而是通过中介者间接通信,从而大大降低对象之间的耦合。

定义​

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

把"多对多"的交互关系变成"一对多":每个对象只认识中介者,中介者认识所有对象。

结构​

让我们从一个问题开始​

假设我们要开发一个多人聊天室。最直接的实现是:每个用户都持有其他所有用户的引用,发消息时直接调用对方的接收方法:

public class User {

private String name;
private List<User> friends = new ArrayList<>();

public void send(String message) {
// 直接给所有朋友发消息
for (User friend : friends) {
friend.receive(message);
}
}

public void receive(String message) {
System.out.println(name + " 收到消息:" + message);
}
}

问题显而易见:

  1. 每个用户都要维护一份"朋友列表",新增一个用户时,所有用户都要更新自己的列表;
  2. 用户之间显式相互引用,耦合极强,任何一方的变化都可能波及其他用户;
  3. 如果聊天室需要新增功能(比如"禁言""群发""消息记录"),就要修改每个用户类。

中介者模式的结构​

引入一个聊天室(中介者),用户之间不再直接通信:

各个角色的职责如下:

  • Mediator(中介者):定义同事对象之间通信的接口,对应图中的 Mediator。
  • ConcreteMediator(具体中介者):实现中介者接口,协调各个同事对象之间的关系,对应图中的 ChatRoom。它需要知道所有同事对象,以便转发消息。
  • Colleague(同事):定义同事对象的接口,每个同事只知道自己的中介者,不知道其他同事的存在。
  • ConcreteColleague(具体同事):实现同事接口,通过中介者与其他同事通信,对应图中的 User。

关键点:同事对象只依赖中介者,同事之间互不引用。原来的"多对多"关系被简化成"一对多"。

代码实现​

我们来实现一个多人聊天室。

定义中介者接口​

public interface Mediator {
void register(User user);

void send(String message, User sender);
}

定义具体中介者(聊天室)​

ChatRoom 负责维护用户列表,并把一条消息转发给除发送者之外的所有用户:

import java.util.ArrayList;
import java.util.List;

public class ChatRoom implements Mediator {

private final List<User> users = new ArrayList<>();

@Override
public void register(User user) {
// 注册用户时,把聊天室的引用交给用户
users.add(user);
user.setMediator(this);
System.out.println(user.getName() + " 已加入聊天室");
}

@Override
public void send(String message, User sender) {
// 转发消息给除发送者外的所有用户
for (User user : users) {
if (user != sender) {
user.receive(message);
}
}
}
}

定义同事接口和具体同事(用户)​

public interface Colleague {
void send(String message);

void receive(String message);
}
public class User implements Colleague {

private final String name;
private Mediator mediator;

public User(String name) {
this.name = name;
}

// 由中介者在注册时注入
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

public String getName() {
return name;
}

@Override
public void send(String message) {
// 用户之间不直接通信,消息统一交给中介者转发
System.out.println(name + " 发送消息:" + message);
mediator.send(message, this);
}

@Override
public void receive(String message) {
System.out.println(name + " 收到消息:" + message);
}
}

客户端使用​

public class Main {

public static void main(String[] args) {
ChatRoom chatRoom = new ChatRoom();

User alice = new User("Alice");
User bob = new User("Bob");
User carol = new User("Carol");

chatRoom.register(alice);
chatRoom.register(bob);
chatRoom.register(carol);

System.out.println("=========================");
alice.send("大家好,我是 Alice!");

System.out.println("=========================");
bob.send("大家好,我是 Bob!");
}
}

输出结果:

Alice 已加入聊天室
Bob 已加入聊天室
Carol 已加入聊天室
=========================
Alice 发送消息:大家好,我是 Alice!
Bob 收到消息:大家好,我是 Alice!
Carol 收到消息:大家好,我是 Alice!
=========================
Bob 发送消息:大家好,我是 Bob!
Alice 收到消息:大家好,我是 Bob!
Carol 收到消息:大家好,我是 Bob!

核心收益:

  1. User 之间零引用,新增用户只需要 chatRoom.register(newUser),已有用户完全无感知;
  2. 新增聊天室功能(禁言、消息记录、表情)只需要修改 ChatRoom,所有 User 无需改动;
  3. 用户类变得简单纯粹:只负责"发消息"和"收消息"。

在开源框架中的应用​

Spring Web MVC:DispatcherServlet 的中介者角色​

Spring Web MVC 的 DispatcherServlet 是中介者模式的经典应用。一个 HTTP 请求到达后,DispatcherServlet 协调多个组件共同完成处理:

  • HandlerMapping:根据请求找到对应的处理器(HandlerExecutionChain);
  • HandlerAdapter:真正调用处理器(Controller)的方法;
  • ViewResolver:根据逻辑视图名解析出具体的 View;
  • HandlerExceptionResolver:处理请求过程中的异常。

如果没有 DispatcherServlet,这些组件需要彼此直接协作,耦合会非常严重。DispatcherServlet 作为中介者,把请求处理流程的各个参与者集中协调起来:

DispatcherServlet#doDispatch 的核心流程(简化):

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 通过 HandlerMapping 找到处理器
HandlerExecutionChain mappedHandler = getHandler(request);

// 2. 通过 HandlerAdapter 调用处理器(Controller 方法),得到 ModelAndView
ModelAndView mv = handlerAdapter.handle(request, response, mappedHandler.getHandler());

// 3. 通过 ViewResolver 解析视图
View view = viewResolver.resolveViewName(mv.getViewName(), locale);

// 4. 渲染视图
view.render(mv.getModel(), request, response);
}

Controller、ViewResolver、HandlerMapping 之间互不引用,它们只认识 DispatcherServlet。新增一种视图解析方式(比如 JSON 视图),只需要新增一个 ViewResolver 实现并注册到容器,DispatcherServlet 自动协调,其他组件无需改动——这正是中介者模式的价值。

适用场景​

  • 当对象之间存在复杂的多对多交互关系,且希望把这些关系集中管理时,可以使用中介者模式,避免对象之间相互引用、网状的依赖。
  • 当多个对象之间的交互行为需要在运行时动态调整,或者希望交互逻辑可以独立演化时,中介者把交互逻辑集中到一处,方便修改。
  • 当对象重用一个代价昂贵,或者对象数量庞大导致直接通信成本过高时(如聊天室、航班调度、UI 控件联动),中介者可以显著降低通信复杂度。
  • 注意:当对象之间的交互简单、数量少时,引入中介者反而会增加复杂度,此时直接通信更合适。

与其他设计模式的关系​

  • 中介者模式可以用**观察者模式(Observer)**实现:中介者可以扮演"主题",同事对象作为"观察者"订阅中介者的通知,实现广播。
  • 门面模式(Facade)与中介者容易混淆:门面提供的是单向的简化接口(客户端 → 子系统),中介者管理的是双向的交互协调(同事 ↔ 同事)。门面用于"简化调用",中介者用于"解耦通信"。
  • 中介者内部的转发逻辑如果越来越复杂,可以结合**责任链模式(Chain of Responsibility)或命令模式(Command)**来组织。

总结​

中介者模式用一个中介对象集中管理一系列对象之间的交互:

  • 对象之间不再显式相互引用,多对多的网状依赖被简化为一对多;
  • 交互逻辑集中在中介者中,方便统一修改和扩展;
  • 新增参与者不需要修改已有参与者,只需要让中介者认识它。

中介者模式的缺点是:中介者本身容易变成一个"上帝对象",承担过多的交互逻辑,变得越来越臃肿;同时,消息经由中介者转发会带来额外的通信开销。因此,中介者模式适合参与者众多、交互复杂的场景(聊天室、航班调度、MVC 的 DispatcherServlet),简单场景下直接通信反而更清晰。