Mediator
一言でいうと
Mediator (メディエーター、 仲介者) は、 振る舞いに関するデザインパターンの一つで、 オブジェクト間の混沌とした依存性を削減します。 パターンは、 オブジェクト間の直接の通信を制限し、 メディエーター・オブジェクトを介してのみの共同作業を強制します。
問題
たとえば、 顧客プロフィールの作成と編集のためのダイアログがあると思ってください。 これは、 テキストフィールド、 チェックボックス、 ボタンなどの様々なフォーム・コントロールでできています。
フォーム要素のいくつかは他の要素と相互に関係します。 たとえば、 「犬を飼っています」 のチェックボックスを選ぶと、 今まで隠れていた犬の名前を入力するためのテキストフィールドが表示されます。 別の例として、 送信ボタンは、 データを保存する前にすべてのフィールドの値を検証します。
フォーム要素のコード内にこのロジックを直接実装することで、 これらの要素のクラスをアプリの他のフォームで再利用するのが非常に困難になります。 たとえば、 このチェックボックスのクラスは、 犬のテキストフィールドに結合されているため、 別のフォーム内では使用できません。 プロフィールのフォームのレンダリングをするには、 関わるすべてのクラスを使用するか、 あるいは、 フォームを使うのをあきらめるかしかありません。
解決策
Mediator パターンによれば、 お互いに独立させたいコンポーネント間では直接の通信を断つべきです。 代わりに、 これらのコンポーネントは、 呼び出しを適切なコンポーネントへ方向づけてくれる、 特別なメディエーター・オブジェクトを呼び出し、 間接的に共同作業を行う必要があります。 そうすることにより、 コンポーネントは多数の同僚のコンポーネントに結合するのではなく、 一つのメディエーター・クラスにのみ依存します。
さきほどのプロフィール編集フォームの例では、 ダイアログ・クラス自体がメディエーターとして機能するかもしれません。 おそらく、 ダイアログ・クラスはその下位要素のすべてをすでに知っているので、 このクラスに新しい依存関係を導入する必要はありません。
最も重要な変更は、 実際のフォーム要素に起こります。 送信ボタンのことを考えてみましょう。 以前は、 ユーザーがボタンをクリックするたびに、 フォームの全要素の値を検証する必要がありました。 今では、 その唯一の仕事は、 クリックについてダイアログに通知することだけです。 この通知を受信すると、 ダイアログ自体が検証を実行するか、 またはその仕事を個々の要素に渡します。 したがって、 ボタンは、 数十のフォーム要素に結び付けられておらず、 ダイアログ・クラスだけに依存します。
これをさらに進めて、 すべての種類のダイアログに共通なインターフェースを抽出すれば、 依存関係をさらに緩めることができます。 インターフェースは、 フォーム要素に発生したイベントをダイアログに通知するのに全フォーム要素が使える通知メソッドを宣言します。 したがって、 送信ボタンは、 そのインターフェースを実装するどんなダイアログとでも動作できるようになります。
こういうふうにして、 Mediator パターンにより、 メディエーター・オブジェクト内のいろいろなオブジェクト間の複雑な関係を内に包むことができます。
現実世界でのたとえ
空港に接近したり、 空港から出発する航空機のパイロットは、 互いに直接連絡を取ることはしません。 代わりに、 滑走路近くのどこかの高い塔に座っている航空交通管制官と話します。 航空交通管制官がいなければ、 パイロットたちは、 空港近くのあらゆる飛行機を気にして、 他の何十人ものパイロットたちと着陸の優先順位について話し合うことになります。 おそらくそれは飛行機の墜落率を急増させることになるでしょう。
その管制塔は飛行全部を管理するわけではありません。 管制塔は、 飛行の末端での制限を守らせるためにあります。 その辺では、 パイロット一人でさばききれないだけの数の関係者が存在するからです。
構造
-
コンポーネント (Component) は、 何らかのビジネス・ロジックを含んだ種々のクラスです。 各コンポーネントは、 メディエーター・インターフェースの型で宣言されたメディエーターへの参照を持っています。 コンポーネントは、 メディエーターの実際のクラスが何かは知りません。 そのため、 別のメディエーターにリンクすることで、 他のプログラムのコンポーネントを再利用することができます。
-
メディエーター (Mediator) インターフェースは、 コンポーネントとの通信するメソッドを宣言します。 通常は、 通知メソッド一つだけからなります。 コンポーネントは、 このメソッドの引数として任意のコンテキストを渡すことができます。 それはオブジェクト自身でもかまいませんが、 受け手のコンポーネントと送り手のクラスが結合しないような方法に限ります。
-
具象メディエーター (Concrete Mediator) は、 様々なコンポーネント間の関係を内に隠します。 具象メディエーターはしばしば、 管理するすべてのコンポーネントへの参照を保持し、 時にはそのライフサイクルを管理することもあります。
-
コンポーネント (Component) は他のコンポーネントの存在を知る必要はありません。 コンポーネント内で、 コンポーネントに対して、 何か重要なことが発生した場合は、 メディエーターにのみ通知する必要があります。 メディエーターは通知を受け取ると、 送り手を簡単に識別でき、 どのコンポーネントの引き金を引けばいいかを決めるには、 その情報だけで十分かもしれません。
コンポーネントの観点からすると、 すべては、 完全なブラックボックスのように見えます。 送り手は誰がそのリクエストを処理することになるのかはわからず、 受け手は誰が最初にリクエストを送信したのかを知りません。
擬似コード
この例では、 Mediator パターンを使用して、 ボタン、 チェックボックス、 テキストラベルなど、 様々な UI クラス間の相互依存関係を解消しています。
ユーザーに起動された要素は、 他の要素と直接通信するのがよさそうに見えても、 そうしません。 代わりに、 要素はそのメディエーターにイベントについて通知するだけです。 周辺情報も、 その通知とともに渡します。
この例では、 認証ダイアログ全体がメディエーターとして機能します。 メディエーターは、 具象要素同士が連携するやり方を知っており、 間接的な通信を主導します。 イベントに関する通知を受信すると、 どの要素がイベントに対応すべきかをダイアログが決定し、 そこへ呼び出しをまわします。
適応性
クラスが他の多くのクラスに密に結合しているため変更が難しい時に、 Mediator パターンを適用してください。
このパターンは、 クラス間の関係のすべてを別のクラスに抽出します。 特定コンポーネントへの変更を、 残りのコンポーネントから隔離します。
他コンポーネントへの依存度が高いため、 違うプログラムのコンポーネントを再利用できない時、 このパターンを使ってください。
Mediator を適用すると、 個々のコンポーネントは他コンポーネントの存在に気づかなくなります。 間接的ではありますが、 コンポーネント同士はメディエーター・オブジェクトを通してまだ通信することができます。 別のアプリケーションでコンポーネントを再利用するには、 新しいメディエーター・クラスを作成する必要があります。
様々な状況で基本的な振る舞いを再利用するだけのために、 とんでもない数のコンポーネントのサブクラスを作成していることに気づいたら、 Mediator を使ってください。
コンポーネント間のすべての関係がメディエーターに含まれているため、 コンポーネント自体は変更せず、 新しいメディエーターを導入するたけで、 これらのコンポーネントが協力して機能するためのまったく新しい方法を簡単に定義できます。
実装方法
-
独立性の強化の恩恵を受けそうな密に結合されたクラスのグループを特定します (例: 保守を容易にする。 クラス再利用の簡易化)。
-
メディエーター・インターフェースを宣言し、 メディエーターと様々なコンポーネント間の望ましい通信プロトコルを記述します。 多くの場合、 コンポーネントから通知を受け取るメソッド一つで十分です。
異なる状況でコンポーネント・クラスを再利用したい場合、 このインターフェースは、 極めて重要な役を果たします。 コンポーネントが一般的インターフェースを介してメディエーターと動作する限り、 コンポーネントを異なる実装のメディエーターとリンクさせることができます。
-
具象メディエーター・クラスを実装します。 このクラスの管理するすべてのコンポーネントへの参照を格納するようにしておくと、 メディエーターのメソッドからどのコンポーネントでも呼ぶことができ、 便利です。
-
さらに進んで、 コンポーネント・オブジェクトの作成と破壊をメディエーターにやらせるようにもできます。 こうすると、 メディエーターは、 ファクトリーやファサードに類似してくるかもしれません。
-
コンポーネントはメディエーター・オブジェクトへの参照を格納すべきです。 この接続は通常、 コンポーネントのコンストラクター内で確立され、 コンストラクターにはメディエーター・オブジェクトが引数として渡されます。
-
コンポーネントのコードを変更して、 他のコンポーネントのメソッドの代わりに、 メディエーターの通知メソッドを呼び出すように します。 他のコンポーネントを呼び出すようなコードを抽出して、 メディエーター・クラスに入れます。 メディエーターがそのコンポーネントから通知を受けるたびに、 このコードを実行します。
長所と短所
- 単一責任の原則。 様々なコンポーネント間の通信を一箇所に抽出することで、 理解と保守を容易にする。
- 開放閉鎖の原則。 実際のクライアントの変更の必要なしに新規メディエーターを導入可能。
- プログラムの様々なコンポーネント間の結合度の削減が可能。
- 個々のコンポーネントの再利用が容易に可能。
- 時間が経つにつれて、 メディエーターは神オブジェクトに進化するかもしれない。
他のパターンとの関係
-
Chain of Responsibility と Command と Mediator と Observer は、 リクエストの送り手と受け手を接続する様々な方法を示します:
- Chain of Responsibility は、 潜在的受け手の動的な連鎖に沿って、 どれか一つが処理するまで、 リクエストを順番に渡します。
- Command は、 送り手と受け手との間で単方向の接続を確立します。
- Mediator は、 送り手と受け手の間の直接の接続を削除し、 メディエーター・オブジェクトを介しての間接的通信を強制します。
- Observer では、 受け手が動的にリクエストの受信申し込みをしたり、 申し込み取り消しをしたりできます。
-
Facade と Mediator は、 多くの密に結合されたクラス間の協力関係を組織するという似た目的があります。
- Facade は、 オブジェクトのサブシステムへの簡略化されたインターフェースを定義しますが、 新しい機能が導入されるわけではありません。 サブシステム自体はファサードを認識しません。 サブシステム内のオブジェクト同士は直接やりとりします。
- Mediator は、 システムのコンポーネント間の通信を一元化します。 コンポーネントはメディエーター・オブジェクトについてのみ知っており、 直接やりとりしません。
-
Mediator と Observer との違いは、 理解に苦しむことがあります。 ほとんどの場合、 これらのパターンのいずれかを実装すればいですが、 両方を同時に適用することもできます。 どうすればそれができるか見てみましょう。
Mediator の主目的は、 システム構成コンポーネント間の相互依存をなくすことです。 その代わりに、 これらのコンポーネントは単一のメディエーター・オブジェクトに依存するようになります。 Observer の主目的は、 オブジェクト間の動的な単方向の接続を確立することにあり、 そこではあるオブジェクトが他のオブジェクトの部下として動作します。
Observer に依存した、 Mediator パターンの有名な実装方法があります。 メディエーター・オブジェクトがパブリッシャーとしての役割を果たし、 他のコンポーネントがサブスクライバーとしてメディエーターのイベントに通知依頼をしたり依頼解除をします。 このように Mediator を実装すると、 Observer と非常に似たよう見えます。
混乱した時は、 Mediator パターンは、 他の方法でも実装できるということを忘れないでください。 たとえば、 同一のメディエーター・オブジェクトにすべてのコンポーネントを恒久的にリンクできます。 この実装方法は、 Observer には似ていませんが、 Mediator パターンの適用例の一つと言えます。
ここで、 プログラム中のすべてのコンポーネントがパブリッシャーとなり、 お互いに動的な接続が許された状況を想像してみてください。 ここでは、 中心となるメディエーター・オブジェクトはなく、 分散されたオブザーバーの集団があるだけです。