mohuneko’s blog

かんばる駆け出しエンジニアのブログです

【Java】Mediator パターン【デザインパターン】

駆け出しエンジニアがデザインパターンをもくもく勉強します

 こんな本で勉強しています🌟

目次

Mediatorパターンについて

Mediatorパターンとは

  • Mediatorパターンは、相談役を通して行動を起こすようにしてもらうパターンです。
  • Mediatorという英単語は"相談役"と考えると分かりやすいです。
  • メンバーが、互いに指示しあっていたら、作業は混乱します。
  • そんなとき、立場の違う相談役を立て、相談役だけから、メンバーへの指示を行うようにします

サンプルプログラム

  • Mediator パターンを使って、ログインダイアログを表示し、名前やパスワードを入力するテキストフィールドや、ボタンの有効/無効状態を、制御する例を取り上げます
  • これらのテキストフィールドやボタンのロジックを各クラスに分散させると、それぞれのオブジェクトは関係し合っているので、お互いをコントロールし合うことになり、大変なことになります
  • このように、多数のオブジェクト間の調整を行う必要があるときに、Mediatoパターンを使います
  • 個々のオブジェクトが互いに通信するのではなく、"相談役"のみと通信し、"相談役"のみに、表示のロジックを記述します
  • 各クラスの役割は以下のようになっています
名前 役割
Mediator 相談役のインターフェースを定めるインターフェース
Colleague メンバのインターフェースを定めるインターフェース
ColleagueButton(ConcreteColleague) Colleagueインターフェースを実装し、ボタンを表すクラス
ColleagueCheckbox(ConcreteColleague) Colleagueインターフェースを実装し、ラジオボタンを表すクラス
ColleagueTextField (ConcreteColleague) Colleagueインターフェースを実装し、テキスト入力を表すクラス
LoginFrame(ConcreteMediator) Mediatorインターフェースを実装し、ログインダイアログを表すクラス

f:id:mohuNeko:20201231171309p:plain

Mediatorインターフェース

  • 相談役を表現するインターフェースです
  • 具体的な相談役(LoginFrame)は、このインターフェースを実装します
  • createColleaguesメソッドで、メンバ(必要なボタンやテキストフィールド)を生成します
  • colleagueChangedメソッドは、メンバから呼ばれるメソッドです。相談役への"相談"に当たります。
public interface Mediator {
    public abstract void createColleagues();  //メンバを生成
    public abstract void colleagueChanged(); //相談役への"相談"
}

Colleagueインターフェース

  • 相談役に相談を持ちかけるメンバを表すインターフェースです
  • 具体的なメンバはこのクラスを実装します
  • setMediatorメソッドは、Mediatorインターフェースを実装したLoginFrameが最初に呼ぶメソッド
  • setColleagueEnabledメソッドは、相談役からの指示に当たります
    • 自分自身を有効/無効に設定するのは、自分ではなく、相談役の判断で決めます
public interface Colleague {
    public abstract void setMediator(Mediator mediator); //相談役を設定
    public abstract void setColleagueEnabled(boolean enabled); //相談役からの指示
}

ColleagueButtonクラス

*ボタンを表すクラスです。Colleagueインターフェースを実装し、LoginFrameクラスと協調動作を行います * setEnabledがtrueなら、ボタンが押せる状態です

import java.awt.Button;

public class ColleagueButton extends Button implements Colleague {
    private Mediator mediator;
    public ColleagueButton(String caption) {
        super(caption);
    }
    public void setMediator(Mediator mediator) {            // Mediatorを保持
        this.mediator = mediator;
    }
    public void setColleagueEnabled(boolean enabled) {      // Mediatorから有効/無効が指示される
        setEnabled(enabled);
    }
}

ColleagueCheckboxクラス

  • チェックボックス(ここではラジオボタン)を表すクラスです。Colleagueインターフェースを実装します。
  • java.awt.event.ItemListenerを実装し、ボタンの状態の変化を、itemStateChangedメソッドでキャッチします
import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;

public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague {
    private Mediator mediator;
    public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) {  // コンストラクタ
        super(caption, group, state);
    }
    public void setMediator(Mediator mediator) {            // Mediatorを保持
        this.mediator = mediator;
    }
    public void setColleagueEnabled(boolean enabled) {      // Mediatorから有効/無効が指示される
        setEnabled(enabled);
    }
    public void itemStateChanged(ItemEvent e) {             // 状態が変化したらMediatorに通知
        mediator.colleagueChanged();
    }
}

ColleagueTextFieldクラス

  • テキストボックスを表すクラスです。Colleagueインターフェースを実装します。
  • java.awt.event.TextListenerを実装し、textValueChangedメソッドでテキストの内容の変化をキャッチします
    • テキストの内容に変更があった場合に、AWTのフレームワーク側から呼び出されます
    • colleagueChangedメソッドを呼び出して、自分の文字列内容が変化したことを報告しています
import java.awt.TextField;
import java.awt.Color;
import java.awt.event.TextListener;
import java.awt.event.TextEvent;

public class ColleagueTextField extends TextField implements TextListener, Colleague {
    private Mediator mediator;
    public ColleagueTextField(String text, int columns) {   //コンストラクタ
        super(text, columns);
    }
    public void setMediator(Mediator mediator) {            // Mediator保持
        this.mediator = mediator;
    }
    public void setColleagueEnabled(boolean enabled) {      // Mediatorから有効、無効が指示される
        setEnabled(enabled);
        setBackground(enabled ? Color.white : Color.lightGray);
    }
    public void textValueChanged(TextEvent e) {             // 文字列が変化したらMediatorに追加
        mediator.colleagueChanged();
    }
}

LoginFrameクラス

  • ログインダイアログを表すクラスです。Mediatorインターフェースを実装します
  • GUIIアプリケーションを作るためのクラスであるjava.awt.Frameのサブクラスです
  • コンストラクタの中で、背景色・レイアウトマネージャ設定・Colleagueの生成・配置・初期状態の設定・表示を行います
  • colleagueChangedメソッドで、各Colleageの有効/無効を判定します
    • 相談役への相談が、colleagueChangedメソッドに集約しています
import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.CheckboxGroup;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class LoginFrame extends Frame implements ActionListener, Mediator {
    private ColleagueCheckbox checkGuest;
    private ColleagueCheckbox checkLogin;
    private ColleagueTextField textUser;
    private ColleagueTextField textPass;
    private ColleagueButton buttonOk;
    private ColleagueButton buttonCancel;

    // コンストラクタ。
    // Colleagueたちを生成し、配置した後に表示を行う。
    public LoginFrame(String title) {
        super(title);
        setBackground(Color.lightGray);
        // レイアウトマネージャを使って4×2のグリッドを作る
        setLayout(new GridLayout(4, 2));
        // Colleagueたちの生成
        createColleagues();
        // 配置
        add(checkGuest);
        add(checkLogin);
        add(new Label("Username:"));
        add(textUser);
        add(new Label("Password:"));
        add(textPass);
        add(buttonOk);
        add(buttonCancel);
        // 有効/無効の初期指定
        colleagueChanged();
        // 表示
        pack();
        show();
    }

    // Colleagueたちを生成する。
    public void createColleagues() {
        // 生成
        CheckboxGroup g = new CheckboxGroup();
        checkGuest = new ColleagueCheckbox("Guest", g, true);
        checkLogin = new ColleagueCheckbox("Login", g, false);
        textUser = new ColleagueTextField("", 10);
        textPass = new ColleagueTextField("", 10);
        textPass.setEchoChar('*');
        buttonOk = new ColleagueButton("OK");
        buttonCancel = new ColleagueButton("Cancel");
        // Mediatorのセット
        checkGuest.setMediator(this);
        checkLogin.setMediator(this);
        textUser.setMediator(this);
        textPass.setMediator(this);
        buttonOk.setMediator(this);
        buttonCancel.setMediator(this);
        // Listenerのセット
        checkGuest.addItemListener(checkGuest);
        checkLogin.addItemListener(checkLogin);
        textUser.addTextListener(textUser);
        textPass.addTextListener(textPass);
        buttonOk.addActionListener(this);
        buttonCancel.addActionListener(this);
    }

    // Colleageからの通知で各Colleageの有効/無効を判定する。
    public void colleagueChanged() {
        if (checkGuest.getState()) { // Guest mode
            textUser.setColleagueEnabled(false);
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(true);
        } else { // Login mode
            textUser.setColleagueEnabled(true);
            userpassChanged();
        }
    }
    // textUserまたはtextPassの変更があった。
    // 各Colleageの有効/無効を判定する。
    private void userpassChanged() {
        if (textUser.getText().length() > 0) {
            textPass.setColleagueEnabled(true);
            if (textPass.getText().length() > 0) {
                buttonOk.setColleagueEnabled(true);
            } else {
                buttonOk.setColleagueEnabled(false);
            }
        } else {
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(false);
        }
    }
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        System.exit(0);
    }
}

Mainクラスで動作確認

import java.awt.*;
import java.awt.event.*;

public class Main {
    static public void main(String args[]) {
        new LoginFrame("Mediator Sample");
    }
}

Mediatorパターンのメリット

  • 表示の有効/無効に関するロジックは複雑になりますが、LoginFrameクラスに集約されているので、改修する場合はLoginFrameクラスのみ修正すればよいことになります
  • 通信経路を少なくすることで、インスタンスが多くなっても、シンプルな設計にできます
  • アプリケーションへの依存性が高い部分は、LoginFrameクラスに集約されているので、LoginFrameクラスの再利用性は低いです

今日のポイント

  • Mediatorパターンは、相談役を通して行動を起こすようにしてもらうパターンです。
  • 関係し合う、多数のオブジェクト間の調整を行う必要があるときに、Mediatoパターンを使います
  • 表示の有効/無効に関するロジックは複雑になりますが、LoginFrameクラスに集約されているので、改修する場合はLoginFrameクラスのみ修正すればよいことになります
  • アプリケーションへの依存性が高い部分は、LoginFrameクラスに集約されているので、LoginFrameクラスの再利用性は低いです

 本日もお疲れ様です😊