java-recipes

ホーム デザインパターン › DP-17

DP-17: Mediator パターン

複数のオブジェクト間の複雑な相互通信を、調停者(Mediator)オブジェクトに集約するパターンです。 各オブジェクトは互いを直接参照せず、Mediator を通じて通信することで、 依存関係を整理して保守性を高めます。

Mediator パターンとは

チャットアプリで5人のユーザーがいる場合、全員が互いを直接参照すると最大10本の依存関係が生まれます。 ユーザーが増えるにつれて依存関係は爆発的に増加し、コードが複雑になります。 Mediator パターンはこの問題を解決します。各ユーザーはチャットルーム(Mediator)だけを知り、 メッセージの配信はすべて Mediator が担います。これにより依存関係が「各ユーザー→Mediator」の形に整理されます。

Mediator パターンの登場人物

  • Mediator(調停者): 通信ロジックを一元管理するインターフェース(例: ChatMediator)
  • ConcreteMediator(具体調停者): 実際の通信処理を実装する(例: ChatRoom)
  • Colleague(同僚): Mediator を通じて通信する各コンポーネント(例: ChatUser)

GUI フレームワークでは、フォームの各入力コンポーネント(テキストボックス・ボタン・チェックボックスなど)が 互いの状態に応じて活性/非活性を切り替える処理を Mediator で管理するのが典型的な使用例です。

サンプルコード

MediatorPatternSample.java
import java.util.ArrayList;
import java.util.List;

public class MediatorPatternSample {

    // Mediator インターフェース
    interface ChatMediator {
        void sendMessage(String message, ChatUser sender);
        void addUser(ChatUser user);
    }

    // Colleague(コンポーネント)抽象クラス
    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 message);
        abstract void receive(String message, String senderName);
    }

    // 具体 Mediator: チャットルーム
    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(String message, ChatUser sender) {
            // 送信者以外の全ユーザーにメッセージを配信
            for (ChatUser user : users) {
                if (user != sender) {
                    user.receive(message, sender.name);
                }
            }
        }
    }

    // 具体 Colleague: 一般ユーザー
    static class GeneralUser extends ChatUser {
        GeneralUser(ChatMediator mediator, String name) {
            super(mediator, name);
        }

        @Override
        public void send(String message) {
            System.out.println("[送信] " + name + ": " + message);
            mediator.sendMessage(message, this);
        }

        @Override
        public void receive(String message, String senderName) {
            System.out.println("[受信] " + name + " <- " + senderName + ": " + message);
        }
    }

    // 具体 Colleague: 管理者ユーザー(全員に管理者通知として配信)
    static class AdminUser extends ChatUser {
        AdminUser(ChatMediator mediator, String name) {
            super(mediator, name);
        }

        @Override
        public void send(String message) {
            System.out.println("[管理者送信] " + name + ": " + message);
            mediator.sendMessage("[管理者通知] " + message, this);
        }

        @Override
        public void receive(String message, String senderName) {
            System.out.println("[管理者受信] " + name + " <- " + senderName + ": " + message);
        }
    }

    public static void main(String[] args) {
        ChatRoom chatRoom = new ChatRoom();

        ChatUser alice = new GeneralUser(chatRoom, "Alice");
        ChatUser bob = new GeneralUser(chatRoom, "Bob");
        ChatUser admin = new AdminUser(chatRoom, "Admin");

        chatRoom.addUser(alice);
        chatRoom.addUser(bob);
        chatRoom.addUser(admin);

        System.out.println();
        alice.send("こんにちは!");
        System.out.println();
        bob.send("はじめまして!");
        System.out.println();
        admin.send("システムメンテナンスのお知らせです");
    }
}

Java 8 では Mediator インターフェースと抽象クラスを組み合わせて実装します。各ユーザーは ChatRoom(Mediator)を介してのみ通信し、互いを直接参照しません。

よくあるミス・注意点

⚠️ Mediator に処理を集めすぎて「神クラス」になる

Mediator パターンは依存関係を整理しますが、通信ロジックを詰め込みすぎると Mediator 自体が肥大化して保守しにくくなります。 Mediator は「誰に転送するか」だけを担い、ビジネスロジックは各 Colleague クラスに残しましょう。

⚠️ Colleague が Mediator をバイパスして直接通信する

Colleague 同士が this の参照を渡し合って 直接メソッドを呼ぶと、Mediator パターンの意味が失われます。 Colleague は必ず Mediator 経由で通信するルールを徹底しましょう。

⚠️ Observer パターンとの混同

Observer パターンは「1対多の通知」を目的とし、通知元はどのオブジェクトが購読しているか意識しません。 Mediator パターンは「多対多の相互通信を仲介」することを目的とし、Mediator が通信の全体像を把握します。 目的が異なるため、用途に応じて選択しましょう。

テストする観点

  • メッセージを送信したとき、送信者自身には届かず、他の全ユーザーに届くこと
  • 管理者が送信したとき、メッセージに「[管理者通知]」プレフィックスが付くこと
  • ユーザーを1人だけ登録した状態でメッセージを送っても、エラーが起きないこと(境界値)
  • ユーザーが0人のとき、メッセージを送っても何も起きないこと(境界値)
  • 同じユーザーを2回追加したとき、メッセージが2回届かないこと(重複登録の確認)
  • Colleague が Mediator のみを知り、他の Colleague インスタンスを直接保持していないこと

GitHub でソースコードを見る →