设计模式:中介者模式

欢迎你来读这篇博客,这篇博客主要是关于中介者模式
其中包括中介者模式的核心思想、适用场景、优缺点、与外观模式、观察者模式、事件总线的区别,以及 Java 后端开发中聊天室 / IM 消息中转系统的完整案例。

序言

在软件开发中,有一种复杂度特别容易被忽略:

多个对象之间互相认识、互相调用,最后形成一张复杂的网。

比如一个聊天室系统里有很多用户。

如果用户之间直接互相发送消息,可能会变成这样:

1
2
3
4
5
6
User A -> User B
User A -> User C
User B -> User A
User B -> User C
User C -> User A
User C -> User B

用户越多,对象之间的关系越复杂。

再比如一个订单系统里,多个模块互相调用:

1
2
3
4
5
6
OrderService -> InventoryService
OrderService -> PaymentService
PaymentService -> OrderService
PaymentService -> CouponService
InventoryService -> OrderService
CouponService -> PaymentService

这种互相引用、互相调用的结构会带来很严重的问题:

  • 类之间耦合严重;
  • 调用链复杂;
  • 修改一个对象可能影响多个对象;
  • 单元测试困难;
  • 业务流程分散;
  • 很容易出现循环依赖;
  • 系统像一团毛线,越拉越紧。

中介者模式就是为了解决这类问题:

用一个中介对象封装多个对象之间的交互,让对象之间不再直接互相引用,而是通过中介者进行协作。

简单说:

中介者模式就是把“对象之间互相乱聊”变成“统一找群主协调”。

这和现实里的群聊很像。

你不需要知道群里每个人的联系方式,只需要把消息发到群里,由群系统负责转发。

如果每个人都私聊所有人,那不是社交,是复杂度爆炸。

正文

chapter 1:什么是中介者模式

中介者模式,英文是 Mediator Pattern,属于行为型设计模式。

它的定义是:

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

这句话可以拆成三层:

  1. 系统中有多个对象需要协作;
  2. 如果对象之间直接互相调用,会形成复杂的网状关系;
  3. 引入一个中介者,让对象只和中介者打交道。

中介者模式的核心是:

把对象之间的多对多关系,转换成对象与中介者之间的一对多关系。

原来的结构:

1
2
3
4
5
A <-> B
A <-> C
B <-> C
B <-> D
C <-> D

使用中介者后:

1
2
3
4
A -> Mediator
B -> Mediator
C -> Mediator
D -> Mediator

对象之间不再直接互相依赖,而是通过中介者协调。

chapter 2:中介者模式的核心角色

中介者模式一般包含四个角色:

  1. Mediator 抽象中介者:定义对象之间通信的接口。
  2. ConcreteMediator 具体中介者:实现协调逻辑,管理同事对象之间的交互。
  3. Colleague 抽象同事类:持有中介者引用,通过中介者与其他对象通信。
  4. ConcreteColleague 具体同事类:真正参与协作的对象。

结构如下:

1
2
3
Colleague A ──┐
Colleague B ──┼──> Mediator
Colleague C ──┘

在聊天室案例中:

中介者模式角色 聊天室示例
Mediator ChatRoom
ConcreteMediator DefaultChatRoom
Colleague ChatUser
ConcreteColleague NormalUser、AdminUser

用户之间不直接互发消息。

用户只调用聊天室:

1
chatRoom.sendMessage(fromUser, message);

聊天室负责把消息转给其他用户。

chapter 3:为什么需要中介者模式

假设没有中介者,用户之间直接通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User {

private final String name;

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

public void sendMessage(String message) {
for (User friend : friends) {
friend.receiveMessage(this.name, message);
}
}

public void receiveMessage(String from, String message) {
System.out.println(from + " 说:" + message);
}
}

这看起来还行。

但问题很快会出现。

如果聊天室需要支持:

  • 用户加入;
  • 用户退出;
  • 群发消息;
  • 私聊消息;
  • 禁言;
  • 踢人;
  • 敏感词过滤;
  • 离线消息;
  • 消息持久化;
  • 管理员广播;
  • 在线用户列表;
  • WebSocket 推送。

如果这些逻辑都写在 User 里,用户类会越来越胖。

而且用户之间需要知道彼此:

1
2
3
userA.addFriend(userB);
userA.addFriend(userC);
userB.addFriend(userA);

这会造成强耦合。

中介者模式的做法是:

用户不直接维护其他用户,只维护一个聊天室引用;用户发消息时交给聊天室,聊天室负责协调消息如何转发。

这样,用户只依赖中介者。

用户之间不直接依赖。

chapter 4:中介者模式解决的核心问题

中介者模式主要解决的是:

多个对象之间复杂的交互关系。

它不是用来简化单个对象逻辑的。

它解决的是对象关系复杂度。

1. 降低对象之间的耦合

对象不直接引用其他对象。

1
user.send(message);

内部交给:

1
chatRoom.sendMessage(this, message);

2. 集中管理交互逻辑

原本散落在多个对象里的交互逻辑,统一收敛到中介者中。

3. 避免网状依赖

多个对象之间不再互相调用,而是统一通过中介者。

4. 便于修改协作规则

如果消息转发规则变化,只需要改中介者,而不是改所有用户对象。

chapter 5:生活中的中介者例子

中介者模式在生活中很常见。

1. 聊天群

用户不需要知道群里每个人的连接,只需要把消息发到群里。

群系统负责分发。

2. 房产中介

买家和卖家不直接反复沟通,而是通过房产中介协调。

3. 机场塔台

飞机之间不直接互相协调起降,而是通过塔台统一调度。

4. 交易所撮合系统

买方和卖方不直接交易,而是通过交易所撮合。

5. 消息队列

生产者和消费者不直接依赖,而是通过 MQ 解耦。

这些都是中介思想:

参与者不直接互相耦合,而是通过一个中心协调者完成交互。

chapter 6:最简单的中介者模式示例

先写一个简单聊天室。

定义中介者接口:

1
2
3
4
5
6
public interface ChatRoom {

void register(ChatUser user);

void sendMessage(ChatUser from, String message);
}

定义用户抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class ChatUser {

protected final String username;

protected final ChatRoom chatRoom;

protected ChatUser(String username, ChatRoom chatRoom) {
this.username = username;
this.chatRoom = chatRoom;
}

public String getUsername() {
return username;
}

public void join() {
chatRoom.register(this);
}

public void send(String message) {
chatRoom.sendMessage(this, message);
}

public abstract void receive(String from, String message);
}

具体用户:

1
2
3
4
5
6
7
8
9
10
11
public class NormalChatUser extends ChatUser {

public NormalChatUser(String username, ChatRoom chatRoom) {
super(username, chatRoom);
}

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

具体聊天室:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList;
import java.util.List;

public class DefaultChatRoom implements ChatRoom {

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

@Override
public void register(ChatUser user) {
users.add(user);

System.out.println(user.getUsername() + " 加入聊天室");
}

@Override
public void sendMessage(ChatUser from, String message) {
for (ChatUser user : users) {
if (!user.equals(from)) {
user.receive(from.getUsername(), message);
}
}
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MediatorDemo {

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

ChatUser mario = new NormalChatUser("Mario", chatRoom);
ChatUser luigi = new NormalChatUser("Luigi", chatRoom);
ChatUser peach = new NormalChatUser("Peach", chatRoom);

mario.join();
luigi.join();
peach.join();

mario.send("大家好,我是 Mario");
}
}

这里用户之间没有互相持有引用。

所有消息都通过 ChatRoom 中介者转发。

chapter 7:案例背景:IM 聊天室消息中转系统

下面用一个更贴近 Java 后端的案例来讲中介者模式:聊天室 / IM 消息中转系统。

需求如下:

  1. 用户可以加入聊天室;
  2. 用户可以退出聊天室;
  3. 用户可以发送群聊消息;
  4. 用户可以发送私聊消息;
  5. 聊天室负责消息路由;
  6. 聊天室负责敏感词过滤;
  7. 聊天室负责禁言校验;
  8. 用户之间不直接互相依赖。

这类场景非常适合中介者模式。

因为用户之间的交互关系很复杂。

如果让用户之间直接调用,会很快失控。

更合理的是:

1
用户 -> 聊天室中介者 -> 目标用户

chapter 8:定义消息对象 ChatMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.time.LocalDateTime;

public class ChatMessage {

private final String fromUserId;

private final String toUserId;

private final String roomId;

private final String content;

private final MessageType type;

private final LocalDateTime createdAt;

public ChatMessage(String fromUserId,
String toUserId,
String roomId,
String content,
MessageType type) {
this.fromUserId = fromUserId;
this.toUserId = toUserId;
this.roomId = roomId;
this.content = content;
this.type = type;
this.createdAt = LocalDateTime.now();
}

public String getFromUserId() {
return fromUserId;
}

public String getToUserId() {
return toUserId;
}

public String getRoomId() {
return roomId;
}

public String getContent() {
return content;
}

public MessageType getType() {
return type;
}

public LocalDateTime getCreatedAt() {
return createdAt;
}
}

消息类型:

1
2
3
4
5
6
public enum MessageType {

GROUP,

PRIVATE
}

chapter 9:定义中介者接口 ChatMediator

1
2
3
4
5
6
7
8
9
10
public interface ChatMediator {

void join(ChatMember member);

void leave(ChatMember member);

void sendGroupMessage(ChatMember from, String content);

void sendPrivateMessage(ChatMember from, String toUserId, String content);
}

这个接口就是中介者。

它负责协调成员之间的通信。

成员不直接找其他成员发消息,而是调用中介者。

chapter 10:定义同事类 ChatMember

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class ChatMember {

protected final String userId;

protected final String username;

protected final ChatMediator mediator;

protected ChatMember(String userId, String username, ChatMediator mediator) {
this.userId = userId;
this.username = username;
this.mediator = mediator;
}

public String getUserId() {
return userId;
}

public String getUsername() {
return username;
}

public void join() {
mediator.join(this);
}

public void leave() {
mediator.leave(this);
}

public void sendGroupMessage(String content) {
mediator.sendGroupMessage(this, content);
}

public void sendPrivateMessage(String toUserId, String content) {
mediator.sendPrivateMessage(this, toUserId, content);
}

public abstract void receive(ChatMessage message);
}

这个类是抽象同事类。

它只知道中介者,不知道其他用户。

这里很关键:

1
protected final ChatMediator mediator;

用户之间不再直接依赖,而是都依赖中介者。

chapter 11:定义普通成员 NormalChatMember

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class NormalChatMember extends ChatMember {

public NormalChatMember(String userId, String username, ChatMediator mediator) {
super(userId, username, mediator);
}

@Override
public void receive(ChatMessage message) {
if (message.getType() == MessageType.GROUP) {
System.out.println(username + " 收到群聊消息:"
+ message.getFromUserId()
+ " 说:"
+ message.getContent());
return;
}

System.out.println(username + " 收到私聊消息:"
+ message.getFromUserId()
+ " 说:"
+ message.getContent());
}
}

chapter 12:定义管理员成员 AdminChatMember

管理员可以接收消息,也可以在后续扩展管理能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AdminChatMember extends ChatMember {

public AdminChatMember(String userId, String username, ChatMediator mediator) {
super(userId, username, mediator);
}

@Override
public void receive(ChatMessage message) {
System.out.println("[管理员] " + username + " 收到消息:"
+ message.getFromUserId()
+ " -> "
+ message.getContent());
}
}

这里为了演示,管理员只是打印不同格式。

真实项目中管理员可能有:

  • 禁言;
  • 踢人;
  • 撤回消息;
  • 审核消息;
  • 群公告;
  • 全员广播。

这些都可以通过中介者协调。

chapter 13:定义具体中介者 DefaultChatMediator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class DefaultChatMediator implements ChatMediator {

private final String roomId;

private final Map<String, ChatMember> members = new HashMap<>();

private final Set<String> mutedUsers = new HashSet<>();

public DefaultChatMediator(String roomId) {
this.roomId = roomId;
}

@Override
public void join(ChatMember member) {
members.put(member.getUserId(), member);

System.out.println(member.getUsername() + " 加入房间:" + roomId);
}

@Override
public void leave(ChatMember member) {
members.remove(member.getUserId());

System.out.println(member.getUsername() + " 离开房间:" + roomId);
}

@Override
public void sendGroupMessage(ChatMember from, String content) {
checkCanSend(from);

String filteredContent = filterSensitiveWords(content);

ChatMessage message = new ChatMessage(
from.getUserId(),
null,
roomId,
filteredContent,
MessageType.GROUP
);

for (ChatMember member : members.values()) {
if (!member.getUserId().equals(from.getUserId())) {
member.receive(message);
}
}
}

@Override
public void sendPrivateMessage(ChatMember from, String toUserId, String content) {
checkCanSend(from);

ChatMember target = members.get(toUserId);

if (target == null) {
throw new IllegalArgumentException("target user not in room, userId = " + toUserId);
}

String filteredContent = filterSensitiveWords(content);

ChatMessage message = new ChatMessage(
from.getUserId(),
toUserId,
roomId,
filteredContent,
MessageType.PRIVATE
);

target.receive(message);
}

public void mute(String userId) {
mutedUsers.add(userId);

System.out.println("用户被禁言,userId = " + userId);
}

public void unmute(String userId) {
mutedUsers.remove(userId);

System.out.println("用户解除禁言,userId = " + userId);
}

private void checkCanSend(ChatMember from) {
if (!members.containsKey(from.getUserId())) {
throw new IllegalStateException("user not in room, userId = " + from.getUserId());
}

if (mutedUsers.contains(from.getUserId())) {
throw new IllegalStateException("user is muted, userId = " + from.getUserId());
}
}

private String filterSensitiveWords(String content) {
if (content == null || content.isBlank()) {
throw new IllegalArgumentException("content can not be blank");
}

return content.replace("badword", "***");
}
}

这个类就是中介者模式中的具体中介者。

它集中管理:

  • 成员加入;
  • 成员退出;
  • 群聊转发;
  • 私聊转发;
  • 禁言校验;
  • 敏感词过滤。

如果没有中介者,这些逻辑可能会散落在每个用户对象中。

chapter 14:客户端使用聊天室中介者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ChatMediatorDemo {

public static void main(String[] args) {
DefaultChatMediator chatRoom = new DefaultChatMediator("room-1001");

ChatMember mario = new NormalChatMember("u1", "Mario", chatRoom);
ChatMember luigi = new NormalChatMember("u2", "Luigi", chatRoom);
ChatMember peach = new NormalChatMember("u3", "Peach", chatRoom);
ChatMember admin = new AdminChatMember("admin", "Admin", chatRoom);

mario.join();
luigi.join();
peach.join();
admin.join();

mario.sendGroupMessage("大家好,我是 Mario");

luigi.sendPrivateMessage("u1", "你好 Mario");

chatRoom.mute("u2");

try {
luigi.sendGroupMessage("我还能说话吗?");
} catch (Exception e) {
System.out.println("发送失败:" + e.getMessage());
}

peach.sendGroupMessage("这里不能说 badword");
}
}

输出类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
Mario 加入房间:room-1001
Luigi 加入房间:room-1001
Peach 加入房间:room-1001
Admin 加入房间:room-1001
Luigi 收到群聊消息:u1 说:大家好,我是 Mario
Peach 收到群聊消息:u1 说:大家好,我是 Mario
[管理员] Admin 收到消息:u1 -> 大家好,我是 Mario
Mario 收到私聊消息:u2 说:你好 Mario
用户被禁言,userId = u2
发送失败:user is muted, userId = u2
Mario 收到群聊消息:u3 说:这里不能说 ***
Luigi 收到群聊消息:u3 说:这里不能说 ***
[管理员] Admin 收到消息:u3 -> 这里不能说 ***

这里可以看到:

  • 用户之间没有直接互相调用;
  • 所有通信都经过 DefaultChatMediator
  • 禁言、过滤、转发规则都集中在中介者中。

chapter 15:中介者模式的关键价值

这个聊天室案例体现了中介者模式的几个关键点。

1. 用户对象更简单

用户只知道:

1
2
mediator.sendGroupMessage(...)
mediator.sendPrivateMessage(...)

不需要维护其他用户列表。

2. 交互规则集中管理

禁言、敏感词、群发、私聊都在中介者中。

3. 对象之间不直接耦合

Mario 不需要知道 Luigi 对象在哪里。

只需要知道目标用户 ID。

4. 扩展交互规则更方便

如果后续要加:

  • 消息持久化;
  • 离线消息;
  • WebSocket 推送;
  • 消息已读回执;
  • 群管理员审核;
  • 机器人回复。

可以在中介者或中介者协作的服务中扩展。

chapter 16:中介者不要变成上帝对象

中介者模式有一个很大的风险:

中介者可能会越来越胖,最后变成上帝对象。

比如 DefaultChatMediator 后续不断增加:

  • 消息存储;
  • 消息审核;
  • 推送;
  • 统计;
  • 风控;
  • 权限;
  • 已读;
  • 离线;
  • 通知;
  • 搜索;
  • 推荐。

最后它会变成几千行。

这就不好了。

中介者应该负责协调,而不是吞掉所有业务细节。

更好的做法是让中介者协调多个服务:

1
2
3
4
5
6
7
ChatMediator
-> MemberRegistry
-> MessageRouter
-> SensitiveWordService
-> MuteService
-> MessageStore
-> PushService

中介者做编排。

具体能力交给专业服务。

不然中介者就从“协调员”进化成“全公司唯一员工”。听起来很强,实际上要猝死。

chapter 17:拆分后的中介者结构

真实项目中可以拆成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ChatMediator {

private final MemberRegistry memberRegistry;

private final MessagePolicyService messagePolicyService;

private final MessageRouter messageRouter;

private final MessageStore messageStore;

public void sendMessage(ChatMessage message) {
messagePolicyService.check(message);

ChatMessage savedMessage = messageStore.save(message);

messageRouter.route(savedMessage);
}
}

这里:

  • MemberRegistry 管理成员;
  • MessagePolicyService 校验禁言、风控、敏感词;
  • MessageStore 负责消息落库;
  • MessageRouter 负责路由消息;
  • ChatMediator 负责协调这些组件。

这就比把所有逻辑写在一个中介者类里更稳。

chapter 18:后端系统中的中介者思想

中介者模式在后端系统中并不少见,只是名字不一定叫 Mediator。

常见体现包括:

1. 聊天室 / IM 房间

用户之间不直接通信,通过房间或会话中介者通信。

2. 事件总线

发布者和订阅者不直接依赖,通过 EventBus 协调。

3. 消息队列

生产者和消费者通过 MQ 解耦。

4. 应用服务

多个领域对象或服务之间的用例协调,有时也带中介者思想。

5. 工作流引擎

不同节点之间不直接调用,由流程引擎协调状态流转。

6. 交易撮合系统

买卖双方通过撮合引擎中介完成交易。

7. UI 组件协调器

多个控件之间通过 Controller 或 Mediator 交互。

chapter 19:Spring Boot 中落地中介者模式

在 Spring Boot 中,中介者可以作为一个 @Service

例如 IM 消息中介者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import org.springframework.stereotype.Service;

@Service
public class ChatMessageMediator {

private final ChatMemberService chatMemberService;

private final MuteService muteService;

private final SensitiveWordService sensitiveWordService;

private final ChatMessageStore chatMessageStore;

private final ChatPushService chatPushService;

public ChatMessageMediator(ChatMemberService chatMemberService,
MuteService muteService,
SensitiveWordService sensitiveWordService,
ChatMessageStore chatMessageStore,
ChatPushService chatPushService) {
this.chatMemberService = chatMemberService;
this.muteService = muteService;
this.sensitiveWordService = sensitiveWordService;
this.chatMessageStore = chatMessageStore;
this.chatPushService = chatPushService;
}

public void sendGroupMessage(String roomId, String fromUserId, String content) {
chatMemberService.checkInRoom(roomId, fromUserId);

muteService.checkNotMuted(roomId, fromUserId);

String filteredContent = sensitiveWordService.filter(content);

ChatMessage message = new ChatMessage(
fromUserId,
null,
roomId,
filteredContent,
MessageType.GROUP
);

ChatMessage savedMessage = chatMessageStore.save(message);

chatPushService.pushToRoom(roomId, fromUserId, savedMessage);
}

public void sendPrivateMessage(String roomId, String fromUserId, String toUserId, String content) {
chatMemberService.checkInRoom(roomId, fromUserId);
chatMemberService.checkInRoom(roomId, toUserId);

muteService.checkNotMuted(roomId, fromUserId);

String filteredContent = sensitiveWordService.filter(content);

ChatMessage message = new ChatMessage(
fromUserId,
toUserId,
roomId,
filteredContent,
MessageType.PRIVATE
);

ChatMessage savedMessage = chatMessageStore.save(message);

chatPushService.pushToUser(toUserId, savedMessage);
}
}

这里 ChatMessageMediator 就是一个工程化的中介者。

它不自己实现所有细节,而是协调多个服务完成消息发送流程。

chapter 20:Spring Boot 子服务示例

成员服务:

1
2
3
4
5
6
7
8
9
import org.springframework.stereotype.Service;

@Service
public class ChatMemberService {

public void checkInRoom(String roomId, String userId) {
System.out.println("校验用户是否在房间,roomId = " + roomId + ",userId = " + userId);
}
}

禁言服务:

1
2
3
4
5
6
7
8
9
import org.springframework.stereotype.Service;

@Service
public class MuteService {

public void checkNotMuted(String roomId, String userId) {
System.out.println("校验用户是否被禁言,roomId = " + roomId + ",userId = " + userId);
}
}

敏感词服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.stereotype.Service;

@Service
public class SensitiveWordService {

public String filter(String content) {
if (content == null || content.isBlank()) {
throw new IllegalArgumentException("content can not be blank");
}

return content.replace("badword", "***");
}
}

消息存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.stereotype.Service;

@Service
public class ChatMessageStore {

public ChatMessage save(ChatMessage message) {
System.out.println("保存消息,roomId = " + message.getRoomId()
+ ",from = " + message.getFromUserId()
+ ",content = " + message.getContent());

return message;
}
}

推送服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.stereotype.Service;

@Service
public class ChatPushService {

public void pushToRoom(String roomId, String excludeUserId, ChatMessage message) {
System.out.println("推送群聊消息,roomId = " + roomId
+ ",excludeUserId = " + excludeUserId
+ ",content = " + message.getContent());
}

public void pushToUser(String userId, ChatMessage message) {
System.out.println("推送私聊消息,userId = " + userId
+ ",content = " + message.getContent());
}
}

Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/chat")
public class ChatController {

private final ChatMessageMediator chatMessageMediator;

public ChatController(ChatMessageMediator chatMessageMediator) {
this.chatMessageMediator = chatMessageMediator;
}

@PostMapping("/rooms/{roomId}/messages")
public void sendGroupMessage(@PathVariable String roomId,
@RequestBody SendGroupMessageRequest request) {
chatMessageMediator.sendGroupMessage(
roomId,
request.getFromUserId(),
request.getContent()
);
}

@PostMapping("/rooms/{roomId}/private-messages")
public void sendPrivateMessage(@PathVariable String roomId,
@RequestBody SendPrivateMessageRequest request) {
chatMessageMediator.sendPrivateMessage(
roomId,
request.getFromUserId(),
request.getToUserId(),
request.getContent()
);
}
}

请求对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SendGroupMessageRequest {

private String fromUserId;

private String content;

public String getFromUserId() {
return fromUserId;
}

public String getContent() {
return content;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SendPrivateMessageRequest {

private String fromUserId;

private String toUserId;

private String content;

public String getFromUserId() {
return fromUserId;
}

public String getToUserId() {
return toUserId;
}

public String getContent() {
return content;
}
}

这个结构下:

  • Controller 不直接调用一堆服务;
  • 各子服务之间不互相乱调;
  • 复杂交互由 ChatMessageMediator 协调;
  • 每个服务职责比较清晰。

chapter 21:中介者模式和外观模式的区别

中介者模式和外观模式很容易混。

它们都可能封装多个对象。

但目的不同。

对比项 中介者模式 外观模式
核心目的 协调多个对象之间的交互 简化外部对子系统的访问
关注点 对象之间怎么通信 客户端怎么简单使用
子对象是否知道中介者 通常知道 通常不知道外观
关系方向 同事对象通过中介者交互 客户端通过外观访问子系统
典型场景 聊天室、组件协调、撮合系统 下单门面、文件上传门面、报表导出门面

一句话区分:

外观模式是对外简化入口。

中介者模式是对内协调交互。

例如:

1
checkoutFacade.checkout(command);

这是外观模式。

1
chatMediator.sendGroupMessage(member, content);

这是中介者模式。

外观模式中,子系统通常不知道外观类存在。

中介者模式中,同事对象通常知道中介者,并通过中介者通信。

chapter 22:中介者模式和观察者模式的区别

中介者模式和观察者模式也容易混。

它们都能降低对象直接耦合。

对比项 中介者模式 观察者模式
核心目的 集中协调多个对象交互 一对多通知状态变化
关系结构 多个同事对象通过中介者协作 Subject 通知多个 Observer
控制逻辑 中介者通常有复杂协调逻辑 Subject 通常只是通知
通信方向 可以双向、多向 通常从被观察者到观察者
示例 聊天室协调用户消息 订单创建后通知库存、积分、消息

观察者模式更像:

1
我发生变化了,通知所有订阅者。

中介者模式更像:

1
你们不要互相找,统一找我协调。

聊天室更适合中介者。

订单创建后通知多个系统,更适合观察者或事件发布。

chapter 23:中介者模式和事件总线的区别

事件总线也能让对象不直接依赖。

例如:

1
eventBus.publish(new OrderCreatedEvent(...));

订阅者监听事件。

中介者和事件总线有相似点,但也不同。

对比项 中介者模式 事件总线
通信方式 通过中介者明确协调 发布事件,订阅者自行响应
调用关系 通常是同步、明确流程 可以同步或异步,松耦合
结果控制 中介者通常知道交互结果 发布者通常不关心订阅结果
逻辑集中度 协调逻辑集中在中介者 逻辑分散在订阅者
示例 聊天室转发消息 订单创建事件通知积分、短信

事件总线更适合事件驱动。

中介者更适合明确协调关系。

例如 IM 聊天室中,发送者通常希望消息明确被路由到目标用户,这更像中介者。

订单创建后通知积分、优惠券、消息系统,这更像事件总线。

chapter 24:中介者模式和代理模式的区别

对比项 中介者模式 代理模式
目的 协调多个对象之间交互 控制对某个对象的访问
对象数量 多个同事对象 通常一个目标对象
结构 多个对象通过中介者通信 客户端通过代理访问目标
示例 聊天室协调用户 Spring AOP 代理 Service

代理模式像“代办人”。

中介者模式像“协调中心”。

chapter 25:中介者模式和责任链模式的区别

对比项 中介者模式 责任链模式
目的 协调对象之间复杂交互 请求沿处理链传递
结构 星形协调 链式传递
控制中心 中介者 链上的处理器
示例 聊天室、塔台、撮合系统 FilterChain、审批链、校验链

责任链是一条线。

中介者是一张网的中心。

chapter 26:中介者模式的优点

1. 降低对象之间的直接耦合

对象不再互相持有复杂引用。

2. 简化对象关系

把多对多关系变成一对多关系。

3. 集中管理交互逻辑

对象之间的协作规则集中到中介者中。

4. 提高可维护性

修改交互规则时,通常只需要修改中介者。

5. 更容易扩展同事对象

新增一个对象,只需要接入中介者,不必和所有其他对象建立关系。

6. 避免循环依赖

多个服务互相调用时,引入中介者可以减少循环依赖风险。

chapter 27:中介者模式的缺点

1. 中介者容易变复杂

所有交互都集中到中介者,容易变成大类。

2. 可能形成新的中心耦合

虽然同事对象解耦了,但它们都依赖中介者。

3. 中介者修改影响范围大

中介者是协调中心,改错可能影响多个对象。

4. 不适合简单交互

如果对象之间关系很简单,引入中介者可能过度设计。

5. 业务逻辑可能被过度集中

如果把所有业务细节都写进中介者,就会破坏职责分离。

chapter 28:适用场景

中介者模式适合以下场景。

1. 多个对象之间存在复杂交互

例如聊天室、UI 组件、流程节点、交易撮合。

2. 对象之间形成网状依赖

如果每个对象都要知道多个其他对象,就可以考虑中介者。

3. 需要集中管理交互规则

例如消息路由、禁言、过滤、撮合、协调。

4. 希望减少循环依赖

多个服务互相调用时,可以用协调服务收敛调用方向。

5. 对象之间通信逻辑经常变化

把变化集中在中介者中更容易维护。

chapter 29:不适合使用的场景

以下场景不建议使用中介者模式。

1. 对象之间交互很简单

如果只是一个对象调用另一个对象,没必要加中介者。

2. 只是为了减少依赖数量

如果没有复杂协作逻辑,只是想少注入几个 Bean,可能更适合外观模式。

3. 中介者已经很胖

如果中介者类越来越大,要拆分服务,而不是继续往里塞。

4. 业务规则应该属于领域对象

不要把本该属于领域对象的规则都搬到中介者里。

5. 事件驱动更合适的场景

如果只是发布事实,让多个订阅者响应,观察者或事件总线可能更合适。

chapter 30:真实项目中的实践建议

1. 中介者负责协调,不负责所有细节

中介者可以编排流程,但具体业务能力应该拆到子服务中。

推荐:

1
2
3
4
ChatMediator
-> MuteService
-> MessageStore
-> PushService

不推荐:

1
ChatMediator 里写所有禁言、存储、推送、风控、统计、搜索逻辑

2. 方法命名要表达协作意图

推荐:

1
2
3
4
sendGroupMessage()
sendPrivateMessage()
matchOrder()
coordinatePayment()

不推荐:

1
2
3
4
handle()
process()
execute()
doSomething()

方法名越泛,职责越容易膨胀。

3. 避免中介者和外观混用不清

如果目的是对外简化接口,是外观。

如果目的是协调多个对象之间的交互,是中介者。

当然真实项目里可能两者都有,但设计意图要清楚。

4. 对复杂中介者做分层

例如 IM 系统可以拆成:

1
2
3
4
ConversationMediator
MessageMediator
MemberMediator
NotificationMediator

不要一个 ImMediator 管全世界。

5. 注意事务边界

如果中介者协调多个数据库操作和外部调用,要注意事务设计。

例如:

1
保存消息 -> 推送消息 -> 更新会话 -> 发送通知

这些动作不一定都适合放在一个本地事务里。

6. 中介者适合稳定流程,不适合频繁魔改

如果交互流程非常动态,可能需要规则引擎、工作流引擎或事件驱动架构。

7. 不要把中介者写成万能 Service

名字叫 Mediator,不代表它可以什么都管。

中介者不是项目里的“总管太监”,它只是协调对象交互的一个模式。

chapter 31:完整案例代码汇总

消息类型

1
2
3
4
5
6
public enum MessageType {

GROUP,

PRIVATE
}

消息对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.time.LocalDateTime;

public class ChatMessage {

private final String fromUserId;

private final String toUserId;

private final String roomId;

private final String content;

private final MessageType type;

private final LocalDateTime createdAt;

public ChatMessage(String fromUserId,
String toUserId,
String roomId,
String content,
MessageType type) {
this.fromUserId = fromUserId;
this.toUserId = toUserId;
this.roomId = roomId;
this.content = content;
this.type = type;
this.createdAt = LocalDateTime.now();
}

public String getFromUserId() {
return fromUserId;
}

public String getToUserId() {
return toUserId;
}

public String getRoomId() {
return roomId;
}

public String getContent() {
return content;
}

public MessageType getType() {
return type;
}

public LocalDateTime getCreatedAt() {
return createdAt;
}
}

中介者接口

1
2
3
4
5
6
7
8
9
10
public interface ChatMediator {

void join(ChatMember member);

void leave(ChatMember member);

void sendGroupMessage(ChatMember from, String content);

void sendPrivateMessage(ChatMember from, String toUserId, String content);
}

抽象同事类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class ChatMember {

protected final String userId;

protected final String username;

protected final ChatMediator mediator;

protected ChatMember(String userId, String username, ChatMediator mediator) {
this.userId = userId;
this.username = username;
this.mediator = mediator;
}

public String getUserId() {
return userId;
}

public String getUsername() {
return username;
}

public void join() {
mediator.join(this);
}

public void leave() {
mediator.leave(this);
}

public void sendGroupMessage(String content) {
mediator.sendGroupMessage(this, content);
}

public void sendPrivateMessage(String toUserId, String content) {
mediator.sendPrivateMessage(this, toUserId, content);
}

public abstract void receive(ChatMessage message);
}

普通成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class NormalChatMember extends ChatMember {

public NormalChatMember(String userId, String username, ChatMediator mediator) {
super(userId, username, mediator);
}

@Override
public void receive(ChatMessage message) {
if (message.getType() == MessageType.GROUP) {
System.out.println(username + " 收到群聊消息:"
+ message.getFromUserId()
+ " 说:"
+ message.getContent());
return;
}

System.out.println(username + " 收到私聊消息:"
+ message.getFromUserId()
+ " 说:"
+ message.getContent());
}
}

管理员成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AdminChatMember extends ChatMember {

public AdminChatMember(String userId, String username, ChatMediator mediator) {
super(userId, username, mediator);
}

@Override
public void receive(ChatMessage message) {
System.out.println("[管理员] " + username + " 收到消息:"
+ message.getFromUserId()
+ " -> "
+ message.getContent());
}
}

具体中介者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class DefaultChatMediator implements ChatMediator {

private final String roomId;

private final Map<String, ChatMember> members = new HashMap<>();

private final Set<String> mutedUsers = new HashSet<>();

public DefaultChatMediator(String roomId) {
this.roomId = roomId;
}

@Override
public void join(ChatMember member) {
members.put(member.getUserId(), member);

System.out.println(member.getUsername() + " 加入房间:" + roomId);
}

@Override
public void leave(ChatMember member) {
members.remove(member.getUserId());

System.out.println(member.getUsername() + " 离开房间:" + roomId);
}

@Override
public void sendGroupMessage(ChatMember from, String content) {
checkCanSend(from);

String filteredContent = filterSensitiveWords(content);

ChatMessage message = new ChatMessage(
from.getUserId(),
null,
roomId,
filteredContent,
MessageType.GROUP
);

for (ChatMember member : members.values()) {
if (!member.getUserId().equals(from.getUserId())) {
member.receive(message);
}
}
}

@Override
public void sendPrivateMessage(ChatMember from, String toUserId, String content) {
checkCanSend(from);

ChatMember target = members.get(toUserId);

if (target == null) {
throw new IllegalArgumentException("target user not in room, userId = " + toUserId);
}

String filteredContent = filterSensitiveWords(content);

ChatMessage message = new ChatMessage(
from.getUserId(),
toUserId,
roomId,
filteredContent,
MessageType.PRIVATE
);

target.receive(message);
}

public void mute(String userId) {
mutedUsers.add(userId);

System.out.println("用户被禁言,userId = " + userId);
}

public void unmute(String userId) {
mutedUsers.remove(userId);

System.out.println("用户解除禁言,userId = " + userId);
}

private void checkCanSend(ChatMember from) {
if (!members.containsKey(from.getUserId())) {
throw new IllegalStateException("user not in room, userId = " + from.getUserId());
}

if (mutedUsers.contains(from.getUserId())) {
throw new IllegalStateException("user is muted, userId = " + from.getUserId());
}
}

private String filterSensitiveWords(String content) {
if (content == null || content.isBlank()) {
throw new IllegalArgumentException("content can not be blank");
}

return content.replace("badword", "***");
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ChatMediatorDemo {

public static void main(String[] args) {
DefaultChatMediator chatRoom = new DefaultChatMediator("room-1001");

ChatMember mario = new NormalChatMember("u1", "Mario", chatRoom);
ChatMember luigi = new NormalChatMember("u2", "Luigi", chatRoom);
ChatMember peach = new NormalChatMember("u3", "Peach", chatRoom);
ChatMember admin = new AdminChatMember("admin", "Admin", chatRoom);

mario.join();
luigi.join();
peach.join();
admin.join();

mario.sendGroupMessage("大家好,我是 Mario");

luigi.sendPrivateMessage("u1", "你好 Mario");

chatRoom.mute("u2");

try {
luigi.sendGroupMessage("我还能说话吗?");
} catch (Exception e) {
System.out.println("发送失败:" + e.getMessage());
}

peach.sendGroupMessage("这里不能说 badword");
}
}

chapter 32:一句话总结

中介者模式的本质是:

用一个中介对象封装多个对象之间的复杂交互,让对象之间不再直接互相引用,而是通过中介者进行协作。

它特别适合:

  • 聊天室;
  • IM 会话;
  • UI 组件协调;
  • 机场塔台调度;
  • 交易撮合;
  • 工作流节点协调;
  • 多个服务之间复杂协作;
  • 避免服务之间网状依赖。

中介者模式解决的不是“对外接口复杂”,那更像外观模式。

它解决的是:

系统内部对象之间的交互关系太复杂。

好的中介者像一个优秀协调员:让每个对象只关心自己的职责,复杂协作由它统一调度。

坏的中介者像一个万能领导:什么都要管,最后所有人都等它批条子。

所以使用中介者模式时,最重要的是一句话:

中介者负责协调,不负责吞掉整个业务世界。

参考资料

  • Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
  • Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
  • Martin Fowler. Patterns of Enterprise Application Architecture.
  • Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software.
  • Spring Framework Documentation: Core Technologies - The IoC Container.
  • Refactoring Guru: Mediator Pattern.
  • SourceMaking: Mediator Design Pattern.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:中介者模式
https://allendericdalexander.github.io/2026/04/01/java/design/18mediator-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议