Mediator
一言でいうと
Mediator (メディエーター、 仲介者) は、 振る舞いに関するデザインパターンの一つで、 オブジェクト間の混沌とした依存性を削減します。 パターンは、 オブジェクト間の直接の通信を制限し、 メディエーター・オブジェクトを介してのみの共同作業を強制します。
![Mediator デザインパターン](/images/patterns/content/mediator/mediator.png?id=0264bd857a231b6ea2d0c537c092e698)
問題
たとえば、 顧客プロフィールの作成と編集のためのダイアログがあると思ってください。 これは、 テキストフィールド、 チェックボックス、 ボタンなどの様々なフォーム・コントロールでできています。
![ユーザー・インターフェースの要素間の混沌とした関係](/images/patterns/diagrams/mediator/problem1-ja.png?id=7e7ee8c102ecf9c450d3c83711d9a930)
アプリケーションの進化につれて、 ユーザー・インターフェースの要素間の関係は混沌となる。
フォーム要素のいくつかは他の要素と相互に関係します。 たとえば、 「犬を飼っています」 のチェックボックスを選ぶと、 今まで隠れていた犬の名前を入力するためのテキストフィールドが表示されます。 別の例として、 送信ボタンは、 データを保存する前にすべてのフィールドの値を検証します。
![UI の要素は相互に依存](/images/patterns/diagrams/mediator/problem2.png?id=072c51eee4dd90c0972866440c87c1c3)
要素は他の要素同士と多くのの関係を持つ可能性あり。 そのため、 ある要素への変更は他の要素に影響を与えるかもしれない。
フォーム要素のコード内にこのロジックを直接実装することで、 これらの要素のクラスをアプリの他のフォームで再利用するのが非常に困難になります。 たとえば、 このチェックボックスのクラスは、 犬のテキストフィールドに結合されているため、 別のフォーム内では使用できません。 プロフィールのフォームのレンダリングをするには、 関わるすべてのクラスを使用するか、 あるいは、 フォームを使うのをあきらめるかしかありません。
解決策
Mediator パターンによれば、 お互いに独立させたいコンポーネント間では直接の通信を断つべきです。 代わりに、 これらのコンポーネントは、 呼び出しを適切なコンポーネントへ方向づけてくれる、 特別なメディエーター・オブジェクトを呼び出し、 間接的に共同作業を行う必要があります。 そうすることにより、 コンポーネントは多数の同僚のコンポーネントに結合するのではなく、 一つのメディエーター・クラスにのみ依存します。
さきほどのプロフィール編集フォームの例では、 ダイアログ・クラス自体がメディエーターとして機能するかもしれません。 おそらく、 ダイアログ・クラスはその下位要素のすべてをすでに知っているので、 このクラスに新しい依存関係を導入する必要はありません。
![UI 要素は、メディエーター・オブジェクトを介して通信すべし。](/images/patterns/diagrams/mediator/solution1-ja.png?id=6be316621539f7893b4387c20cc13aba)
UI 要素は、 メディエーター・オブジェクトを介して間接的に通信すべし。
最も重要な変更は、 実際のフォーム要素に起こります。 送信ボタンのことを考えてみましょう。 以前は、 ユーザーがボタンをクリックするたびに、 フォームの全要素の値を検証する必要がありました。 今では、 その唯一の仕事は、 クリックについてダイアログに通知することだけです。 この通知を受信すると、 ダイアログ自体が検証を実行するか、 またはその仕事を個々の要素に渡します。 したがって、 ボタンは、 数十のフォーム要素に結び付けられておらず、 ダイアログ・クラスだけに依存します。
これをさらに進めて、 すべての種類のダイアログに共通なインターフェースを抽出すれば、 依存関係をさらに緩めることができます。 インターフェースは、 フォーム要素に発生したイベントをダイアログに通知するのに全フォーム要素が使える通知メソッドを宣言します。 したがって、 送信ボタンは、 そのインターフェースを実装するどんなダイアログとでも動作できるようになります。
こういうふうにして、 Mediator パターンにより、 メディエーター・オブジェクト内のいろいろなオブジェクト間の複雑な関係を内に包むことができます。
現実世界でのたとえ
![航空交通管制塔](/images/patterns/diagrams/mediator/live-example.png?id=aa1de3cb7b63aa623e63578a1e43399a)
航空機のパイロットは、 次に誰が着陸するかを決める時に、 互いに直接話すことはしない。 すべての通信は管制塔を通して行われる。
空港に接近したり、 空港から出発する航空機のパイロットは、 互いに直接連絡を取ることはしません。 代わりに、 滑走路近くのどこかの高い塔に座っている航空交通管制官と話します。 航空交通管制官がいなければ、 パイロットたちは、 空港近くのあらゆる飛行機を気にして、 他の何十人ものパイロットたちと着陸の優先順位について話し合うことになります。 おそらくそれは飛行機の墜落率を急増させることになるでしょう。
その管制塔は飛行全部を管理するわけではありません。 管制塔は、 飛行の末端での制限を守らせるためにあります。 その辺では、 パイロット一人でさばききれないだけの数の関係者が存在するからです。
構造
![Mediator デザインパターンの構造](/images/patterns/diagrams/mediator/structure.png?id=1f2accc7820ecfe9665b6d30cbc0bc61)
![Mediator デザインパターンの構造](/images/patterns/diagrams/mediator/structure-indexed.png?id=a82d4cf1b92a4f72af32f231ffd21131)
-
コンポーネント (Component) は、 何らかのビジネス・ロジックを含んだ種々のクラスです。 各コンポーネントは、 メディエーター・インターフェースの型で宣言されたメディエーターへの参照を持っています。 コンポーネントは、 メディエーターの実際のクラスが何かは知りません。 そのため、 別のメディエーターにリンクすることで、 他のプログラムのコンポーネントを再利用することができます。
-
メディエーター (Mediator) インターフェースは、 コンポーネントとの通信するメソッドを宣言します。 通常は、 通知メソッド一つだけからなります。 コンポーネントは、 このメソッドの引数として任意のコンテキストを渡すことができます。 それはオブジェクト自身でもかまいませんが、 受け手のコンポーネントと送り手のクラスが結合しないような方法に限ります。
-
具象メディエーター (Concrete Mediator) は、 様々なコンポーネント間の関係を内に隠します。 具象メディエーターはしばしば、 管理するすべてのコンポーネントへの参照を保持し、 時にはそのライフサイクルを管理することもあります。
-
コンポーネント (Component) は他のコンポーネントの存在を知る必要はありません。 コンポーネント内で、 コンポーネントに対して、 何か重要なことが発生した場合は、 メディエーターにのみ通知する必要があります。 メディエーターは通知を受け取ると、 送り手を簡単に識別でき、 どのコンポーネントの引き金を引けばいいかを決めるには、 その情報だけで十分かもしれません。
コンポーネントの観点からすると、 すべては、 完全なブラックボックスのように見えます。 送り手は誰がそのリクエストを処理することになるのかはわからず、 受け手は誰が最初にリクエストを送信したのかを知りません。
擬似コード
この例では、 Mediator パターンを使用して、 ボタン、 チェックボックス、 テキストラベルなど、 様々な UI クラス間の相互依存関係を解消しています。
![Mediator パターン例の構造](/images/patterns/diagrams/mediator/example.png?id=3151c153533e816e226be0ef977211e8)
UI ダイアログ・クラスの構造
ユーザーに起動された要素は、 他の要素と直接通信するのがよさそうに見えても、 そうしません。 代わりに、 要素はそのメディエーターにイベントについて通知するだけです。 周辺情報も、 その通知とともに渡します。
この例では、 認証ダイアログ全体がメディエーターとして機能します。 メディエーターは、 具象要素同士が連携するやり方を知っており、 間接的な通信を主導します。 イベントに関する通知を受信すると、 どの要素がイベントに対応すべきかをダイアログが決定し、 そこへ呼び出しをまわします。
// メディエーター・インターフェースは、様々なイベントに関してコンポーネン
// トがメディエーターに通知するために使用するメソッドを宣言。メディエー
// ターは、これらのイベントに反応し、実行を他のコンポーネントに渡すことが
// できる。
interface Mediator is
method notify(sender: Component, event: string)
// 具象メディエーター・クラス。個々のコンポーネント間の複雑に絡み合った関
// 係は解きほぐされて、メディエーターへ移動。
class AuthenticationDialog implements Mediator is
private field title: string
private field loginOrRegisterChkBx: Checkbox
private field loginUsername, loginPassword: Textbox
private field registrationUsername, registrationPassword,
registrationEmail: Textbox
private field okBtn, cancelBtn: Button
constructor AuthenticationDialog() is
// すべてのコンポーネント・オブジェクトを作成。その際、現在のメ
// ディエーターをコンストラクターに渡し、リンクを確立する。
// ひとつのコンポーネントに何かが起きると、それはメディエーターに通知
// される。通知を受け取った後、メディエーターは自分で何らかのことを行
// うか、リクエストを他のコンポーネントに渡す。
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
if (loginOrRegisterChkBx.checked)
title = "Log in"
// 1. ログイン・フォームのコンポーネントを表示。
// 2. 登録フォームのコンポーネントを隠す。
else
title = "Register"
// 1. 登録フォームのコンポーネントを表示。
// 2. ログイン・フォームのコンポーネントを隠す。
if (sender == okBtn && event == "click")
if (loginOrRegister.checked)
// ログイン資格情報を使用してユーザーを探すことを試みる。
if (!found)
// ログイン・フィールドの上にエラーメッセージを表示。
else
// 1. 登録フィールドからのデータを使用してユーザー・ア
// カウントを作成。
// 2. そのユーザーをログイン。
// ……
// コンポーネントは、メディエーター・オブジェクトとメディエーター・イン
// ターフェースを介して通信を行う。このため、同じコンポーネントのクラスを
// 違うメディエーター・オブジェクトと結びつけることによって、違う状況下で
// 使用することが可能。
class Component is
field dialog: Mediator
constructor Component(dialog) is
this.dialog = dialog
method click() is
dialog.notify(this, "click")
method keypress() is
dialog.notify(this, "keypress")
// 具象コンポーネント同士は話をしない。唯一の通信手段は、メディエーターへ
// 通知を送ること。
class Button extends Component is
// ……
class Textbox extends Component is
// ……
class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
// ……
適応性
クラスが他の多くのクラスに密に結合しているため変更が難しい時に、 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 パターンの適用例の一つと言えます。
ここで、 プログラム中のすべてのコンポーネントがパブリッシャーとなり、 お互いに動的な接続が許された状況を想像してみてください。 ここでは、 中心となるメディエーター・オブジェクトはなく、 分散されたオブザーバーの集団があるだけです。