「Mediator」パターンとは?サンプルを踏まえてわかりやすく解説!【Java】

  • LINEで送る
「Mediator」パターンとは?サンプルを踏まえてわかりやすく解説!

 本記事ではGoFのデザインパターンのプログラムの振る舞いに関するパターンの一つである「Mediator」パターンを解説します。このパターンを一言で説明するならば、「複数のオブジェクト間の依存性を解消し、オブジェクト間の直接の通信をMediatorオブジェクトを介してのみ作業を行うようにする方法」と言えるでしょう。「Mediator」パターンをサンプルを踏まえて解説します。

Mediator(メディエーター)パターンとは

 Mediatorパターンとは、「オブジェクト同士が直接やり取りするのではなく、仲介役(Mediator) を介してやり取りを行うことで、クラス間の依存関係を減らす」ためのデザインパターンです。

✅ 日常生活での例え:

 スマートホームを例に考えてみましょう。エアコン、照明、カーテンをすべて自分で動作させようとすると管理が大変ですよね?でもスマートスピーカー(例:Google HomeやAlexa)に「おはよう」と一言いえば、照明がつき、カーテンが開き、エアコンがONになります。このスマートスピーカーが「Mediator」にあたります。

Mediatorパターンの構成要素

構成要素説明
Mediator(仲介者)同僚オブジェクト間のやり取りを管理・調整します
ConcreteMediator(具体的仲介者)Mediatorの実装。具体的な調整ロジックを持ちます
Colleague(同僚)Mediatorを通じて他のColleagueとやり取りするクラス
ConcreteColleague(具体的同僚)Colleagueの実装。Mediatorに連絡して、他のColleagueと間接的に連携します

Mediatorパターンの書き方

❌ Mediatorを使わない場合(コードが絡み合って大変※スパゲッティコード)

 Mediatorを利用しない場合、全ての動作を自分自身で行う必要があります。これは大変非効率であり、クライアントの責務が大きすぎます。そのためスマートスピーカーに指示を出して、その指示を受けて各家電を動かす方が効率的です。

// 電気のスイッチ
public class Light {
    public void turnOn() { System.out.println("照明をつけます"); }
    public void turnOff() { System.out.println("照明を消します"); }
}

// カーテンの開け閉め
public class Curtain {
    public void open() { System.out.println("カーテンを開けます"); }
    public void close() { System.out.println("カーテンを閉めます"); }
}

// エアコン
public class AirConditioner {
    public void turnOn() { System.out.println("エアコンをつけます"); }
    public void turnOff() { System.out.println("エアコンを消します"); }
}

 上記の家電を自分でnewしてインスタンス化し、それぞれのコマンドを実行します。めんどくさいですね〜〜〜

public class Main {
    public static void main(String[] args) {
        Light light = new Light();
        Curtain curtain = new Curtain();
        AirConditioner ac = new AirConditioner();

        light.turnOff();
        curtain.close();
        ac.turnOff();
        :
        :
        :
        // いくつもインスタンスをnewして実行する必要がある
    }
}

Mediatorを使う場合

 では次にMediatorを利用して、スマートスピーカーの仕組みを実装します。まずは下記の表通りに実装していきます。

構成要素実際の実装
MediatorMediator
※ インターフェース
ConcreteMediatorSmartHomeMediator
※Mediatorを実装
ColleagueColleague
※ インターフェース
ConcreteColleague・Light
・Curtain
・AirConditioner
※ Colleagueを実装

 まずはColleagueとConcreteColleagueを実装していきます。

public interface Colleague {
    void receive(EventType event);
}

 Colleagueを実装して、それぞれのスマート家電とその機能を作ります。

// 電気を操作するクラス
public class Light implements Colleague{

    public void turnOn() { 
        System.out.println("照明をつけます"); 
    }
    public void turnOff() { 
        System.out.println("照明を消します"); 
    }

    /**
     * Lightクラスは、Colleagueインターフェースを実装しています。
     * 受け取ったイベントに応じて、照明を操作します。
     */
    @Override
    public void receive(EventType event) {
        switch (event) {
            case MORNING_MODE:
                turnOn();
                break;
            case NIGHT_MODE:
                turnOff();
                break;
            default:
                break;
        }
    }
}
// カーテンを操作するクラス
public class Curtain implements Colleague {

    public void open() { 
        System.out.println("カーテンを開けます");
    }
    public void close() { 
        System.out.println("カーテンを閉めます"); 
    }

    /**
     * Curtainクラスは、Colleagueインターフェースを実装しています。
     * 受け取ったイベントに応じて、カーテンを操作します。
     */
    @Override
    public void receive(EventType event) {
        switch (event) {
            case MORNING_MODE:
                open();
                break;
            case NIGHT_MODE:
                close();
                break;
            default:
                break;
        }
    }
}
// エアコンを操作するクラス
public class AirConditioner implements Colleague {

    public void turnOn() { 
        System.out.println("エアコンをつけます"); 
    }
    public void turnOff() { 
        System.out.println("エアコンを消します"); 
    }

    /**
     * AirConditionerクラスは、Colleagueインターフェースを実装しています。
     * 受け取ったイベントに応じて、エアコンを操作します。
     */
    @Override
    public void receive(EventType event) {
        switch (event) {
            case MORNING_MODE:
                turnOn();
                break;
            case NIGHT_MODE:
                turnOff();
                break;
            default:
                break;
        }
    }
}

 それぞれの家電はイベントを受け取ったら、イベントごとの動作を行います。イベントはEnumで定義していますが、本記事ではあまり重要ではないので省略します。

 次にMediatorのインターフェースを作ります。このMediatorは受け取ったevent情報をColleagueに伝播させる役割があります。

public interface Mediator {
    void notify(EventType event);
}

 次に、上記のMediatorを実装する形でSmartHomeMediatorを作ります。本記事の例ではイベントをデバイスに伝えるだけの機能を実装しています。

public class SmartHomeMediator implements Mediator {
    private AirConditioner airConditioner;
    private Curtain curtain;
    private Light light;

    public void setDevices(Light light, Curtain curtain, AirConditioner ac) {
        this.light = light;
        this.curtain = curtain;
        this.airConditioner = ac;
    }

    @Override
    public void notify(EventType event) {
        airConditioner.receive(event);
        curtain.receive(event);
        light.receive(event);
    }
}

 では実際にSmartHomeMediatorを利用して朝モードと夜モードを起動してみます。

public class MediatorSample {
    public static void main(String[] args) {
        Light light = new Light();
        Curtain curtain = new Curtain();
        AirConditioner ac = new AirConditioner();

        SmartHomeMediator mediator = new SmartHomeMediator();
        mediator.setDevices(light, curtain, ac);

        // 朝モード
        System.out.println("---部屋を朝モードにします---");
        mediator.notify(EventType.MORNING_MODE);
        // 夜モード
        System.out.println("---部屋を夜モードにします---");
        mediator.notify(EventType.NIGHT_MODE);
    }
}
>> 実行結果
---部屋を朝モードにします---
エアコンをつけます
カーテンを開けます
照明をつけます
---部屋を夜モードにします---
エアコンを消します
カーテンを閉めます
照明を消します

 これでMediatorパターンの実装は完了しました。一例ではあるのでいくつか書き方はありますが、1つのMediatorを介して複数のコンポーネントに指示を出すことができればMediatorパターンと言えるでしょう。

?疑問点?

 本記事のサンプルではSmartHomeMediator内にColleagueを実装したクラスをフィールドで持っています。そのフィールドに値を注入するのはsetterを利用しています(コンストラクタでも可)。

 筆者は最初、SmartHomeMediatorのコンストラクタでnewすれば良い(SmartHomeMediator内でインスタンスを生成すればいい)、と思いました。ただそれだと問題がありました。

問題点説明
柔軟性が低下するSmartHomeMediatorが特定の Light や Curtain 実装に直接依存することになる
再利用性が低下するMediatorは「仲介役」なので、どんなColleagueでも受け入れられるように設計すべき
テストがしにくいインスタンスSmartHomeMediator内で生成すると、そのインスタンスのmockに差し替えることができない
依存性逆転の原則 (DIP) に反する具象クラスではなく抽象(インターフェース)に依存すべき
シングル・レスポンシビリティ原則 (SRP) に違反するオブジェクトの生成まで担うと、「仲介」と「生成」という2つの責務を持つことになり一貫性が失われる

 このようにSmartHomeMediatorのコンストラクタでColleagueクラスをnewするのにはいくつか弊害があります。もしこの方法で実装する場合はFacadeパターンに類似します。以下にMediatorパターンとFacadeパターン役割を記載します。

相違点MediatorパターンFacadeパターン
役割オブジェクト同士の「連携調整」外部からの「簡略インターフェース」提供
Colleagueとの関係双方向(Colleague → Mediator → 他のColleague)一方向(Facade → サブシステム)
Colleagueの生成外部で生成し注入(依存性注入)Facadeが内部で生成することが多い
結合度疎結合を意図(オブジェクト間の依存を切る)内部構造を隠蔽(結合は強くなる傾向)

 このようにMediatorパターンとFacadeパターンは似て非なるものです。状況に応じて適宜使用していきましょう。Facadeパターンの解説は下記の記事で紹介しています。

メリットとデメリット

メリット

メリット説明
依存性の切り込みColleague同士はMediatorのみを知っていれば良い
連携ロジックの集結中央に一括することで変更に強い

デメリット

デメリット説明
Mediatorの肥大化処理が増えるとMediatorが複雑になりがち
Colleagueにインターフェース実装が必要コード量は増える
Colleagueがモードを解釈する責務を持つ抽象度が高い分、内部での条件分岐が増える可能性あり

 Mediatorには上記のメリットとデメリットがあります。これらのメリットとデメリットからMediatorパターンが有効なシチュエーションは下記があります。

  • オブジェクト同士の相互依存が複雑になっているとき
  • あるイベントをトリガーに複数の処理を同時に行いたいとき
  • オブジェクトの追加・削除に柔軟に対応したいとき

まとめ

 Mediatorパターンは、複数のコンポーネントが相互にやり取りするシステムにおいて、関係をシンプルに保ちつつ、管理を集約するのに非常に有効な手法です。

 日常の「リモコンによる家電制御」のように、1つのハブ(仲介者)を中心にして、個々の要素が連携するイメージを持つと理解しやすくなります。

 本記事で利用したサンプルケースのクラス図は下記になります。

 最後に「Mediator」パターンとは「複数のオブジェクト間の依存性を解消し、オブジェクト間の直接の通信をメディエーターオブジェクトを介してのみ作業を行うようにする方法」です。うまく使っていきましょう。

 本記事で利用したコードは下記のgit上で公開しております。

https://github.com/tamotech1028/mediator

最新の投稿

SNSでもご購読できます。

コメント

コメントを残す