19|中介者:把同事之间的交互规则集中管理
配套代码:GitHub 仓库 · 本篇完整源码 · 行为测试。使用 JDK 25 与 Gradle,包名为
com.hanserwei.patterns.mediator。
系列导航:Java 25 设计模式学习指南
一次文章评审有多名编辑。若每个 Reviewer 都保存其他评审者列表,新增成员、排除发送者和划分评审室的规则就会复制到每个人身上。成员之间的引用关系也越来越复杂。
ReviewRoom 集中保存成员和转发规则。Reviewer 只知道自己所属的房间,发表意见时把消息交给房间;接收意见时只更新收件箱,不再递归转发。
从问题提炼设计意图
中介者封装一组对象的交互方式,减少同事对象之间直接依赖,使协作规则能够独立调整。
对象职责与协作关系
| 示例角色 | 职责 |
|---|---|
ReviewRoom |
具体中介者,登记成员并路由意见 |
Reviewer |
同事对象,通过房间与其他成员协作 |
messages |
同事本地收件箱,只记录已派发消息 |
Demo |
创建房间并安排一次评审互动 |
classDiagram
ReviewRoom o-- Reviewer : members
Reviewer --> ReviewRoom : comments
ReviewRoom --> Reviewer : receives
代码思路:变化应该落在哪个对象上
join 同时负责创建和登记 Reviewer,减少“构造了成员却忘记注册”的无效状态。同名成员被拒绝,防止回执中的姓名无法唯一表达发送者;复杂系统应进一步使用稳定成员标识。
Reviewer.comment 调用 room.send(this, message)。ReviewRoom 验证发送者在本房间,遍历成员并跳过发送者,把带来源名称的消息转发给其他人。规则只写一次,同事无需知道彼此名单。
receive 只记录消息,不再调用 comment。发送意图与接收通知是两个不同动作,明确区分能够避免循环传播。测试用三个成员观察转发,并用两个房间证明消息不会跨房间泄漏。
关键实现与独立运行
配套仓库中的包名是 com.hanserwei.patterns.mediator,源码目录为 src/main/java/com/hanserwei/patterns/mediator/。仓库地址统一见系列导航。以下展示关键文件的完整内容;其余角色和测试在同一仓库中,每个顶级类型各占一个文件。
ReviewRoom.java:
package com.hanserwei.patterns.mediator;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/** 集中处理评审者之间转发规则的中介者. */
public final class ReviewRoom {
/** 已登记的参与者,按加入顺序转发消息. */
private final List<Reviewer> reviewers = new ArrayList<>();
/** 创建并登记参与者;同名成员会造成歧义,因此拒绝重复. */
public Reviewer join(String name) {
Objects.requireNonNull(name, "name");
for (Reviewer reviewer : reviewers) {
if (reviewer.name().equals(name)) {
throw new IllegalArgumentException("Duplicate reviewer: " + name);
}
}
Reviewer reviewer = new Reviewer(name, this);
reviewers.add(reviewer);
return reviewer;
}
/** 将成员意见转发给其他成员,不回送给发送者. */
void send(Reviewer sender, String message) {
if (!reviewers.contains(sender)) {
throw new IllegalArgumentException("Unknown sender");
}
Objects.requireNonNull(message, "message");
for (Reviewer reviewer : reviewers) {
if (reviewer != sender) {
reviewer.receive(sender.name() + ":" + message);
}
}
}
}
Reviewer.java:
package com.hanserwei.patterns.mediator;
import java.util.ArrayList;
import java.util.List;
/** 只知道中介者、无需持有其他评审者引用的同事对象. */
public final class Reviewer {
/** 评审者名称. */
private final String name;
/** 负责协作规则的中介者. */
private final ReviewRoom room;
/** 已收到的意见. */
private final List<String> inbox = new ArrayList<>();
/** 由评审室在登记时创建参与者. */
Reviewer(String name, ReviewRoom room) {
this.name = name;
this.room = room;
}
/** 返回成员名称. */
public String name() {
return name;
}
/** 把意见交给评审室路由. */
public void comment(String message) {
room.send(this, message);
}
/** 接收中介者派发的消息,不再次转发. */
void receive(String message) {
inbox.add(message);
}
/** 返回已收到意见的快照. */
public List<String> messages() {
return List.copyOf(inbox);
}
}
Demo.java 展示调用方如何装配这些对象:
package com.hanserwei.patterns.mediator;
/** 演示本章对象的装配方式和可观察结果. */
public final class Demo {
/** 禁止实例化演示入口. */
private Demo() {}
/** 运行独立示例;args 为未使用的命令行参数. */
public static void main(String[] args) {
ReviewRoom room = new ReviewRoom();
Reviewer ada = room.join("Ada");
Reviewer lin = room.join("Lin");
ada.comment("Looks good");
System.out.println(lin.messages());
}
}
在配套代码仓库根目录运行;Windows 使用 gradlew.bat 替换 ./gradlew:
./gradlew runMediator
./gradlew test --tests 'com.hanserwei.patterns.mediator.PatternTest'
示例的业务输出如下,省略 Gradle 自身的任务提示:
[Ada:Looks good]
用测试确认模式的行为
发送者没有回声,其他成员各收到一次意见;不同房间互不影响;重复姓名加入被拒绝。
对应测试位于 src/test/java/com/hanserwei/patterns/mediator/PatternTest.java。建议先运行现有测试,再改动一个协作环节,观察哪个断言能够发现问题。
常见用法
- 表单控件之间存在联动校验与启用禁用规则。
- 聊天、评审、会议等协作对象需要统一路由规则。
- 多个业务组件之间有复杂协调,直接引用形成网状依赖。
适用边界与容易踩的坑
中介者不是把耦合消灭,而是把交互知识集中到一个位置。如果所有业务逻辑都塞进 ReviewRoom,它会变成难以维护的大对象;应把内容处理、持久化和权限计算留给各自职责。
本例只有一种房间实现,因此没有为了角色对称再增加空的中介者接口。若未来 Reviewer 需要被不同协调实现复用,再抽取 Room 接口会更有价值。
现实评审还需要离开房间、成员生命周期、异步失败和持久历史。本例是单线程内存协作,转发顺序由列表顺序确定,没有可靠消息交付保证。
与相近模式比较
观察者描述主题向订阅者广播事实;中介者描述同事把交互意图交给协调者处理。中介者内部可以用观察者发送事件,但它掌握的路由和协作规则通常更多。
动手练习
增加主持人角色,让某类消息只发送给主持人。修改路由规则时保持 Reviewer.comment 不变,并增加离开房间后继续发言的失败测试,确定成员退出后的对象生命周期。
系列导航:Java 25 设计模式学习指南