概要
複数のオブジェクトが互いに直接参照し合うと、依存関係が網目状に複雑化します。Mediator パターンは、オブジェクト間の通信をすべて仲介者(Mediator)に集約し、各オブジェクト(Colleague)は Mediator だけを知っていればよい設計にします。チャットルーム、フォーム入力の連動制御、イベントバスなどが典型的な適用場面です。この記事では、チャットルームを題材に、一般ユーザーと管理者ユーザーのメッセージ配信を Mediator で仲介する実装を示します。record によるメッセージの不変化、List.copyOf による防御的コピー、Java 21 の sealed interface でユーザーロールを型安全に表現する方法も確認します。
使いどころ
チャットルームで送信者以外の全参加者にメッセージを配信する仲介オブジェクトを実装する
画面フォームのコンポーネント連動(例: 都道府県セレクトが変わったら市区町村セレクトの選択肢を更新)を Mediator に集約する
マイクロサービス間のイベント通知をイベントバス(Mediator)経由に統一し、サービス同士の直接依存を排除する
コード例
import java.util.ArrayList;
import java.util.List;
public class MediatorPatternDemo {
record Message(String content, String senderName) {
@Override public String toString() {
return "[" + senderName + "]: " + content;
}
}
interface ChatMediator {
void sendMessage(Message message, ChatUser sender);
void addUser(ChatUser user);
List<ChatUser> getUsers();
}
static abstract class ChatUser {
protected final ChatMediator mediator;
protected final String name;
ChatUser(ChatMediator mediator, String name) {
this.mediator = mediator; this.name = name;
}
abstract void send(String content);
abstract void receive(Message message);
}
static class ChatRoom implements ChatMediator {
private final List<ChatUser> users = new ArrayList<>();
@Override public void addUser(ChatUser user) {
users.add(user);
System.out.println(user.name + " が参加");
}
@Override
public void sendMessage(Message msg, ChatUser sender) {
for (var user : users) {
if (user != sender) user.receive(msg);
}
}
@Override public List<ChatUser> getUsers() {
return List.copyOf(users);
}
}
static class GeneralUser extends ChatUser {
GeneralUser(ChatMediator m, String name) {
super(m, name);
}
@Override public void send(String content) {
var msg = new Message(content, name);
System.out.println("[送信] " + msg);
mediator.sendMessage(msg, this);
}
@Override public void receive(Message msg) {
System.out.println("[受信] " + name + " <- " + msg);
}
}
static class AdminUser extends ChatUser {
AdminUser(ChatMediator m, String name) {
super(m, name);
}
@Override public void send(String content) {
var msg = new Message("[管理者通知] " + content, name);
System.out.println("[管理者送信] " + msg);
mediator.sendMessage(msg, this);
}
@Override public void receive(Message msg) {
System.out.println("[管理者受信] " + name
+ " <- " + msg);
}
}
public static void main(String[] args) {
var room = new ChatRoom();
var alice = new GeneralUser(room, "Alice");
var bob = new GeneralUser(room, "Bob");
var admin = new AdminUser(room, "Admin");
room.addUser(alice);
room.addUser(bob);
room.addUser(admin);
alice.send("こんにちは!");
admin.send("メンテナンスのお知らせです");
}
}Version Coverage
record でメッセージを不変オブジェクト化し、List.copyOf で簡潔に防御的コピーを返せる。var も利用可。
// Java 17: record で不変なメッセージオブジェクト
record Message(String content, String senderName) {}
public void sendMessage(Message message, ChatUser sender) {
for (var user : users) {
if (user != sender) {
user.receive(message);
}
}
}
public List<ChatUser> getUsers() {
return List.copyOf(users); // 防御的コピー
}Library Comparison
注意点
Mediator 自体が肥大化しやすい。ロジックが増えてきたら Mediator を分割するか、イベント駆動アーキテクチャを検討する
Colleague が Mediator 以外の Colleague を直接呼び出すと、パターンの意味がなくなる。コードレビューで直接参照がないか確認する
getUsers() で内部リストをそのまま返すと外部から変更される恐れがある。List.copyOf で防御的コピーを返すこと
Mediator と Observer は似ているが、Observer は1対多の通知が基本。Mediator は多対多の双方向通信の整理が目的。使い分けを意識する
FAQ
Observer は1つの Subject が複数の Observer に一方向に通知します。Mediator は複数の Colleague 間の双方向通信を仲介し、通信ロジックを Mediator に集約します。
メッセージの種類やユーザーロールごとに Mediator を分割するか、各処理をハンドラークラスに委譲して Mediator 自体は振り分けだけに絞ります。
はい。Swing の JDialog がコンポーネント間の連動を仲介するのは Mediator の一種です。現代の MVVM や Redux も広義には Mediator の役割を担っています。