欢迎你来读这篇博客,这篇博客主要是关于中介者模式。 其中包括中介者模式的核心思想、适用场景、优缺点、与外观模式、观察者模式、事件总线的区别,以及 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 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:中介者模式的核心角色 中介者模式一般包含四个角色:
Mediator 抽象中介者 :定义对象之间通信的接口。
ConcreteMediator 具体中介者 :实现协调逻辑,管理同事对象之间的交互。
Colleague 抽象同事类 :持有中介者引用,通过中介者与其他对象通信。
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 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 消息中转系统。
需求如下:
用户可以加入聊天室;
用户可以退出聊天室;
用户可以发送群聊消息;
用户可以发送私聊消息;
聊天室负责消息路由;
聊天室负责敏感词过滤;
聊天室负责禁言校验;
用户之间不直接互相依赖。
这类场景非常适合中介者模式。
因为用户之间的交互关系很复杂。
如果让用户之间直接调用,会很快失控。
更合理的是:
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 }
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()); } }
这里为了演示,管理员只是打印不同格式。
真实项目中管理员可能有:
禁言;
踢人;
撤回消息;
审核消息;
群公告;
全员广播。
这些都可以通过中介者协调。
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 通常只是通知
通信方向
可以双向、多向
通常从被观察者到观察者
示例
聊天室协调用户消息
订单创建后通知库存、积分、消息
观察者模式更像:
中介者模式更像:
聊天室更适合中介者。
订单创建后通知多个系统,更适合观察者或事件发布。
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.
启示录 富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。