配套代码: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 设计模式学习指南

上一篇:迭代器 · 下一篇:备忘录