中介者模式
从聊天室网状依赖说起
最初的聊天室让每个 User 直接持有其他所有用户的引用——5 个用户就有 20 条引用关系。新加入 1 个用户时,要通知所有已有用户更新引用;某个用户下线时,所有人都要删除对它的引用。这是典型的"网状依赖",N 个对象两两耦合,复杂度是 O(N²)。
中介者模式引入一个调停人(ChatRoom):所有用户只和 ChatRoom 交互,互不直接认识。N 个用户都只需要 1 条到 ChatRoom 的引用,复杂度降到 O(N)。航空塔台就是现实中的中介者——飞机不直接互相通话,统一通过塔台调度。
🔍 定义
中介者模式(Mediator)用一个中介对象来封装一系列对象之间的交互,使各对象之间不需要显式地相互引用,从而降低耦合度,并可以独立地改变它们之间的交互。
⚠️ 不使用中介者存在的问题
聊天室中,每个用户直接持有其他用户的引用来发送消息——用户间两两耦合:
| MediatorBadExample.java |
|---|
| package com.example.behavioral.mediator;
/**
* 中介者模式 - 反例
* 问题:用户直接持有所有联系人引用,耦合度呈 O(n²) 增长
*/
public class MediatorBadExample {
public static void main(String[] args) {
UserBad alice = new UserBad("Alice");
UserBad bob = new UserBad("Bob");
UserBad carol = new UserBad("Carol");
// ❌ 每个用户都要持有其他用户的引用
alice.addContact(bob);
alice.addContact(carol);
bob.addContact(alice);
bob.addContact(carol);
carol.addContact(alice);
carol.addContact(bob);
alice.sendMessage("大家好!");
// 新增用户 Dave?每个人都要手动 addContact(dave) ❌
}
}
class UserBad {
private final String name;
private final java.util.List<UserBad> contacts = new java.util.ArrayList<>();
public UserBad(String name) { this.name = name; }
public void addContact(UserBad user) { contacts.add(user); }
public void sendMessage(String msg) {
System.out.println(name + " 发送: " + msg);
// ❌ 直接调用每个联系人
contacts.forEach(c -> c.receive(name, msg));
}
public void receive(String from, String msg) {
System.out.println(name + " 收到来自 " + from + " 的消息: " + msg);
}
}
|
🏗️ 设计模式结构说明
%%{init: {'themeVariables': {'noteBkgColor': 'transparent', 'noteBorderColor': '#768390'}}}%%
classDiagram
classDef default fill:transparent,stroke:#768390
class ChatMediator {
<<interface>>
+sendMessage(message, sender) void
+addUser(user) void
}
class ChatRoom {
-users: List~ChatUser~
+addUser(user) void
+sendMessage(message, sender) void
}
class ChatUser {
-name: String
-mediator: ChatMediator
+send(message) void
+receive(from, message) void
}
ChatMediator <|.. ChatRoom
ChatUser o--> ChatMediator
ChatRoom o--> ChatUser
note for ChatMediator "抽象中介者(Mediator)"
note for ChatRoom "具体中介者(ConcreteMediator)"
note for ChatUser "同事类(Colleague)"
各 ChatUser 只持有 ChatMediator 引用,不再直接持有其他用户——N 个用户共享 1 个中介者。
💻 设计模式举例说明
| MediatorExample.java |
|---|
| package com.example.behavioral.mediator;
import java.util.ArrayList;
import java.util.List;
/**
* 中介者模式 - 正例
* ChatRoom 作为中介者统一协调,用户只与中介者交互
*/
public class MediatorExample {
public static void main(String[] args) {
ChatMediator room = new ChatRoom("技术交流群");
ChatUser alice = new ChatUser("Alice", room);
ChatUser bob = new ChatUser("Bob", room);
ChatUser carol = new ChatUser("Carol", room);
room.register(alice);
room.register(bob);
room.register(carol);
alice.send("大家好!"); // 中介者广播给其他所有人 ✅
bob.send("Alice 你好!");
// ✅ 新增用户 Dave:只需 room.register(dave),其他用户代码不变
}
}
// 中介者接口
interface ChatMediator {
void register(ChatUser user);
void broadcast(ChatUser sender, String message);
}
// 具体中介者:聊天室(统一协调所有用户通信)
class ChatRoom implements ChatMediator {
private final String name;
private final List<ChatUser> users = new ArrayList<>();
public ChatRoom(String name) { this.name = name; }
@Override
public void register(ChatUser user) {
users.add(user);
System.out.println(user.getName() + " 加入 [" + name + "]");
}
@Override
public void broadcast(ChatUser sender, String message) {
// 发送给除发送者以外的所有人
users.stream()
.filter(u -> u != sender)
.forEach(u -> u.receive(sender.getName(), message));
}
}
// 同事类:用户(只持有中介者引用,不持有其他用户)
class ChatUser {
private final String name;
private final ChatMediator mediator; // ✅ 只与中介者交互
public ChatUser(String name, ChatMediator mediator) {
this.name = name;
this.mediator = mediator;
}
public String getName() { return name; }
public void send(String message) {
System.out.println("[" + name + "] 发送: " + message);
mediator.broadcast(this, message); // 委托给中介者
}
public void receive(String from, String message) {
System.out.println("[" + name + "] 收到来自 " + from + " 的消息: " + message);
}
}
|
⚖️ 优缺点
优点:
- 将网状引用(N*N)简化为星状引用(N+1),显著降低耦合
- 组件彼此独立,可以复用
- 集中管理对象间的交互逻辑,方便修改
缺点:
- 中介者本身可能变成"上帝类",承担过多职责
- 随着组件增多,中介者内部逻辑越来越复杂
🔗 与其它模式的关系
相似模式防混淆:
| 模式 |
通信方向 |
职责 |
| 中介者(Mediator) |
双向:组件 ↔ 中介者 ↔ 组件 |
协调组件间双向通信 |
| 外观(Facade) |
单向:客户端 → 外观 → 子系统 |
简化子系统的访问接口 |
| 观察者(Observer) |
主题 → 观察者(单向广播) |
通知依赖者状态变化 |
🗂️ 应用场景
- 多个对象之间存在复杂的双向依赖关系(如聊天室、空中交通管制、GUI 表单联动)
- 希望将组件间的交互逻辑集中管理,方便维护
- Spring:
ApplicationEventPublisher 和 @EventListener 承担了中介者职责
- 航空管制系统(飞机通过塔台协调,不直接通信)
🏭 工业视角
与观察者模式的本质区别
中介者和观察者都能解耦对象,但应用场景截然不同:
| 维度 |
观察者模式 |
中介者模式 |
| 交互形态 |
有条理的单向广播(主题 → 多个订阅者) |
错综复杂的双向多对多交互 |
| 参与者身份 |
要么是观察者,要么是被观察者 |
每个组件既发消息也收消息 |
| 中心职责 |
只做消息路由,不含业务逻辑 |
封装组件间具体的协调逻辑 |
| 适用时机 |
交互关系比较清晰、有规律 |
交互关系错综复杂、维护成本高 |
EventBus 是观察者还是中介者?
Guava EventBus 虽然也有中心节点,但它只做消息路由,不包含任何业务协调逻辑,
参与者之间仍是单向的发布-订阅关系——本质上是**观察者模式**的实现框架,而非中介者模式。
顺序控制是中介者的独特优势
观察者的通知是"广播",无法保证执行顺序;而中介者可以在内部精确编排调用次序,这在 GUI 表单联动等场景中至关重要:
| 中介者控制 UI 联动顺序 |
|---|
| @Override
public void handleEvent(Component component, String event) {
if (component.equals(selection)) {
String selected = selection.select();
if ("register".equals(selected)) {
// 中介者按需编排:先隐藏、再显示、最后更新提示文案
passwordInput.hide();
repeatedPswdInput.show();
hintText.setText("请设置密码并确认"); // 顺序由中介者掌控
}
}
}
|
中介者易成为「上帝类」
将所有组件的交互逻辑集中到中介者,随着组件数量增多,中介类本身会变得庞大难维护。
使用前应评估:交互关系是否真的足够复杂,值得引入中介者?
若组件交互关系比较清晰,直接引用或观察者往往是更轻量的选择。