如何手写一个中介者模式?Java 代码怎么实现?
简化版
手写中介者模式的关键是定义一个中介者接口,让同事对象只调用中介者,不直接调用彼此。具体中介者保存同事对象集合,并负责把请求转发或协调给合适的对象。
详细版
以聊天室为例:
- 用户之间不直接互相发送消息;
- 用户只把消息交给聊天室;
- 聊天室决定把消息发给哪些用户。
核心代码结构:
interface ChatRoom {
void sendMessage(User from, String message);
}
class User {
private final String name;
private final ChatRoom chatRoom;
public User(String name, ChatRoom chatRoom) {
this.name = name;
this.chatRoom = chatRoom;
}
public void send(String message) {
chatRoom.sendMessage(this, message);
}
public void receive(String message) {
System.out.println(name + " received: " + message);
}
public String getName() {
return name;
}
}
用户只依赖聊天室,不依赖其他用户。聊天室负责统一转发消息,这就是中介者模式。
完整版教学
一、先明确需求
假设我们要写一个多人聊天室。
最直接的写法是让用户对象保存其他用户列表:
class User {
private List<User> friends;
}
但这样会带来几个问题:
- 每个用户都要知道其他用户;
- 添加、删除用户时,很多对象都要同步变化;
- 群聊、私聊、禁言、频道隔离等规则会散落到用户对象里;
- 用户对象越来越重。
更好的做法是引入聊天室作为中介者。用户只管发送和接收,消息分发规则交给聊天室。
二、定义中介者接口
interface ChatRoom {
void register(User user);
void sendMessage(User from, String message);
}
这个接口表达两件事:
- 用户可以加入聊天室;
- 用户可以把消息交给聊天室发送。
用户对象不需要知道聊天室内部如何保存成员,也不需要知道消息发给谁。
三、实现具体中介者
import java.util.ArrayList;
import java.util.List;
class GroupChatRoom implements ChatRoom {
private final List<User> users = new ArrayList<>();
@Override
public void register(User user) {
if (!users.contains(user)) {
users.add(user);
}
}
@Override
public void sendMessage(User from, String message) {
for (User user : users) {
if (user != from) {
user.receive(from.getName() + ": " + message);
}
}
}
}
这里的 GroupChatRoom 就是具体中介者。
它保存所有用户,并负责消息分发。以后如果要加黑名单、禁言、敏感词过滤,也可以在聊天室协调层扩展,而不是让用户对象互相判断。
四、定义同事对象
class User {
private final String name;
private final ChatRoom chatRoom;
public User(String name, ChatRoom chatRoom) {
this.name = name;
this.chatRoom = chatRoom;
this.chatRoom.register(this);
}
public void send(String message) {
chatRoom.sendMessage(this, message);
}
public void receive(String message) {
System.out.println(name + " received -> " + message);
}
public String getName() {
return name;
}
}
用户对象只做两件事:
send():把消息交给中介者;receive():接收中介者转发的消息。
它不需要保存其他用户引用。
五、客户端使用
public class MediatorDemo {
public static void main(String[] args) {
ChatRoom room = new GroupChatRoom();
User alice = new User("Alice", room);
User bob = new User("Bob", room);
User cindy = new User("Cindy", room);
alice.send("大家好");
bob.send("欢迎 Alice");
}
}
调用过程是:
Alice.send()
↓
ChatRoom.sendMessage()
↓
GroupChatRoom 遍历其他用户
↓
Bob.receive() / Cindy.receive()
用户之间没有互相调用,但消息仍能正常流转。
六、这个例子体现了哪些设计点
- 依赖方向变简单:用户依赖聊天室,而不是依赖所有其他用户;
- 协作逻辑集中:群发规则在聊天室里;
- 扩展更容易:加私聊、禁言、频道,可以在聊天室层增加规则;
- 同事对象更纯粹:用户对象只保留自身行为。
不过也要注意,如果聊天室逻辑越来越复杂,应继续拆分,比如把权限、过滤、路由拆成独立组件,再由中介者组合它们。
七、常见误区与追问
实现时要让同事只报告意图,不直接操纵其他同事。表单有3个控件时,复选框变化通知 Mediator,由它决定文本框是否启用、按钮是否可点;复选框不应持有按钮引用。中介者的处理方法还要防止程序性更新再次触发同一事件形成递归。
| 检查维度 | 判定依据 |
|---|---|
| Colleague | 维护自身状态并通知事件 |
| Mediator | 读取必要状态并协调其他同事 |
Checkbox -> Mediator -> TextBox + Button
易错点:中介者调用同事更新时,要区分用户事件和程序性同步。
- 误区:ConcreteMediator 应继承所有组件。 它只需持有或注册同事引用,不是组件子类集合。
- 追问:Mediator 接口用 notify 还是类型方法? 事件少可用明确方法,事件多时可用类型化事件,避免字符串分支。
- 误区:同事不能直接改变自身状态。 自身不变量仍由同事管理,跨对象影响才通知中介者。
- 追问:如何防止事件重入? 使用更新来源、状态比较或重入保护,避免无变化也再次通知。
- 追问:测试应如何分层? 单测同事自身行为,再测试中介者在3个组件间的协调结果。
八、加强记忆
手写中介者模式时,抓住“同事不互调,中介来协调”。接口定义通信入口,具体中介者保存参与对象并执行协作规则,同事对象只通知中介者和接收结果。聊天室例子最典型:用户不直接认识其他用户,而是把消息交给聊天室统一分发。