カナデKanade

17. メディエーターパターン (Mediator)

お互いにからみ合ったオブジェクトが直接呼び合わず、仲介者1つを通して一緒に働くようにするメディエーターパターンについて学びましょう。

ひとことで言うと
呼び合っていたオブジェクトどうしがお互いを知らないようにして、何かが起きたら仲介者1つだけに知らせるようにするパターンです。だれが何をするかは仲介者が決めます。

問題: 機器どうしが直接呼び合うコード

スマートホームを作るとしましょう。機器ごとに決まったルールがあります。

  • アラームが鳴ったら、コーヒーメーカーがコーヒーをいれて、カーテンが開きます。
  • テレビをつけたら、照明が暗くなります。
  • 外出ボタンを押したら、テレビと照明が消えて、カーテンが閉まります。

まず思いつくのは、機器が次に動かす機器たちを直接持っておいて、呼ぶ方法です。

【コーヒーメーカー】を設計しよう:
    〈いれる〉を作ろう():
        「· コーヒーメーカー: コーヒーをいれます」を出力しよう

【カーテン】を設計しよう:
    〈開ける〉を作ろう():
        「· カーテン: 開きます」を出力しよう

    〈閉める〉を作ろう():
        「· カーテン: 閉まります」を出力しよう

【照明】を設計しよう:
    〈明るさ〉を作ろう(【数字】の『度合い』):
        枠「· 照明: 明るさ {『度合い』}」を出力しよう

(参考:機器どうしが直接お互いを知っています。テレビは照明を知っています)
【テレビ】を設計しよう:
    『照明』を空っぽにしよう

    最初に作られる時(『照明』)次のようにしよう:
        『私』の『照明』を『照明』にしよう

    〈つける〉を作ろう():
        「テレビ: つきました」を出力しよう
        『私』の『照明』の〈明るさ〉(30)を実行しよう

    〈消す〉を作ろう():
        「· テレビ: 消えました」を出力しよう

(参考:アラームはコーヒーメーカーとカーテンを知っています)
【アラーム】を設計しよう:
    『コーヒーメーカー』を空っぽにしよう
    『カーテン』を空っぽにしよう

    最初に作られる時(『コーヒーメーカー』, 『カーテン』)次のようにしよう:
        『私』の『コーヒーメーカー』を『コーヒーメーカー』にしよう
        『私』の『カーテン』を『カーテン』にしよう

    〈鳴らす〉を作ろう():
        「アラーム: ジリリリ!」を出力しよう
        『私』の『コーヒーメーカー』の〈いれる〉()を実行しよう
        『私』の『カーテン』の〈開ける〉()を実行しよう

(参考:外出ボタンはテレビ、照明、カーテンを知っています)
【外出ボタン】を設計しよう:
    『テレビ』を空っぽにしよう
    『照明』を空っぽにしよう
    『カーテン』を空っぽにしよう

    最初に作られる時(『テレビ』, 『照明』, 『カーテン』)次のようにしよう:
        『私』の『テレビ』を『テレビ』にしよう
        『私』の『照明』を『照明』にしよう
        『私』の『カーテン』を『カーテン』にしよう

    〈押す〉を作ろう():
        「外出ボタン: 押されました」を出力しよう
        『私』の『テレビ』の〈消す〉()を実行しよう
        『私』の『照明』の〈明るさ〉(0)を実行しよう
        『私』の『カーテン』の〈閉める〉()を実行しよう

『コーヒーメーカー』を新しい【コーヒーメーカー】()にしよう
『カーテン』を新しい【カーテン】()にしよう
『照明』を新しい【照明】()にしよう
『テレビ』を新しい【テレビ】(『照明』)にしよう
『アラーム』を新しい【アラーム】(『コーヒーメーカー』, 『カーテン』)にしよう
『外出ボタン』を新しい【外出ボタン】(『テレビ』, 『照明』, 『カーテン』)にしよう

『アラーム』の〈鳴らす〉()を実行しよう
『テレビ』の〈つける〉()を実行しよう
『外出ボタン』の〈押す〉()を実行しよう

実行結果
アラーム: ジリリリ!
· コーヒーメーカー: コーヒーをいれます
· カーテン: 開きます
テレビ: つきました
· 照明: 明るさ 30
外出ボタン: 押されました
· テレビ: 消えました
· 照明: 明るさ 0
· カーテン: 閉まります

ちゃんと動きます。でも、機器とルールが増えると痛くなります。

  • 機器どうしが網のようにからみ合っています。 テレビは照明を、アラームはコーヒーメーカーとカーテンを、外出ボタンはテレビと照明とカーテンを知っています。機器が増えるほど、線がクモの巣のように増えていきます。
  • 機器をほかの家に持っていけません。【アラーム】は作る時からコーヒーメーカーとカーテンを受け取らなければなりません。コーヒーメーカーのない家では、アラーム1つ使うのも大変です。
  • ルールを変えるには、機器を直さなければなりません。「テレビをつけたらカーテンも閉める」ができたら、【テレビ】がカーテンまで知らなければなりません。テレビのクラス、テレビのコンストラクター、テレビを作る行を全部直すことになります。テレビはテレビなだけなのに。
  • 家全体のルールがひと目で見えません。 朝に何が起きるか知るには【アラーム】を、外出の時のことを知るには【外出ボタン】を開いてみなければなりません。ルールが機器の中に散らばっています。

解決策: 機器のあいだに仲介者を1つ置く

メディエーターパターンは、機器どうしの線をすべて切って、真ん中に仲介者を1つ置きます。

  1. 仲介者の約束を決めます。「何があったかを受け取る」1つで十分です。
  2. 機器は仲介者1つだけを知っています。自分の仕事をしたあと、「こんなことがありました」と仲介者に知らせるだけです。
  3. 仲介者が家全体のルールを知っています。知らせを受けたら、だれが何をするかを決めてやらせます。
  4. 機器どうしは、お互いをまったく知りません。

たとえ: 空港の管制塔

空港には飛行機が何十機も行き来しますが、パイロットどうしが無線で「私が先に降ります」と決めたりはしません。みんな管制塔とだけ話します。管制塔が滑走路の様子を見て「Aは着陸、Bは待機してください」と決めてくれます。飛行機はほかの飛行機を知らなくても、管制塔1つだけを知っていればいいんです。管制塔こそが仲介者です。

だれが何をするの?

役割 この例では やること
仲介者の約束 【仲介者】(規定) 〈知らせを受ける〉を約束します
具体的な仲介者 【家の管理人】 機器たちを知っていて、何かが起きたらだれが何をするかを決めます
機器の骨組み 【機器】 仲介者につなぐ方法と、仲介者に知らせる方法を渡します
機器たち 【テレビ】、【アラーム】、【カーテン】など 自分の仕事だけをして、何かあったら仲介者に知らせます

カナデで作る

同じスマートホームを、メディエーターパターンで作り直してみましょう。

(参考:ステップ1 — 仲介者の約束。機器から「こんなことがありました」を受け取ります)
【仲介者】を規定しよう:
    〈知らせを受ける〉がなければならない(【文字列】の『出来事』)

(参考:ステップ2 — すべての機器の骨組み。機器はほかの機器を知らず、仲介者1つだけを知っています)
【機器】を設計しよう:
    『仲介者』を空っぽに隠そう

    〈つなぐ〉を作ろう(【仲介者】の『仲介者』):
        『私』の『仲介者』を『仲介者』にしよう

    〈知らせる〉を作ろう(【文字列】の『出来事』):
        『私』の『仲介者』の〈知らせを受ける〉(『出来事』)を実行しよう

【機器】をもとにして【コーヒーメーカー】を設計しよう:
    〈いれる〉を作ろう():
        「· コーヒーメーカー: コーヒーをいれます」を出力しよう

【機器】をもとにして【カーテン】を設計しよう:
    〈開ける〉を作ろう():
        「· カーテン: 開きます」を出力しよう

    〈閉める〉を作ろう():
        「· カーテン: 閉まります」を出力しよう

【機器】をもとにして【照明】を設計しよう:
    〈明るさ〉を作ろう(【数字】の『度合い』):
        枠「· 照明: 明るさ {『度合い』}」を出力しよう

(参考:テレビ、アラーム、外出ボタンは自分の仕事をして、仲介者に知らせるだけです。その次のことは知りません)
【機器】をもとにして【テレビ】を設計しよう:
    〈つける〉を作ろう():
        「テレビ: つきました」を出力しよう
        『私』の〈知らせる〉(「テレビオン」)を実行しよう

    〈消す〉を作ろう():
        「· テレビ: 消えました」を出力しよう

【機器】をもとにして【アラーム】を設計しよう:
    〈鳴らす〉を作ろう():
        「アラーム: ジリリリ!」を出力しよう
        『私』の〈知らせる〉(「アラーム」)を実行しよう

【機器】をもとにして【外出ボタン】を設計しよう:
    〈押す〉を作ろう():
        「外出ボタン: 押されました」を出力しよう
        『私』の〈知らせる〉(「外出」)を実行しよう

(参考:ステップ3 — 家の管理人(仲介者)。「何かが起きたら、だれが何をするか」がすべてここに集まっています)
【仲介者】に従う【家の管理人】を設計しよう:
    『コーヒーメーカー』を空っぽに隠そう
    『カーテン』を空っぽに隠そう
    『照明』を空っぽに隠そう
    『テレビ』を空っぽに隠そう

    最初に作られる時(『コーヒーメーカー』, 『カーテン』, 『照明』, 『テレビ』)次のようにしよう:
        『私』の『コーヒーメーカー』を『コーヒーメーカー』にしよう
        『私』の『カーテン』を『カーテン』にしよう
        『私』の『照明』を『照明』にしよう
        『私』の『テレビ』を『テレビ』にしよう

    〈知らせを受ける〉を作ろう(【文字列】の『出来事』):
        『出来事』によって分けよう:
            「アラーム」の場合:
                『私』の『コーヒーメーカー』の〈いれる〉()を実行しよう
                『私』の『カーテン』の〈開ける〉()を実行しよう
            「テレビオン」の場合:
                『私』の『照明』の〈明るさ〉(30)を実行しよう
            「外出」の場合:
                『私』の『テレビ』の〈消す〉()を実行しよう
                『私』の『照明』の〈明るさ〉(0)を実行しよう
                『私』の『カーテン』の〈閉める〉()を実行しよう

(参考:ステップ4 — 機器を作って、知らせる側の機器を同じ仲介者につなぎます)
『コーヒーメーカー』を新しい【コーヒーメーカー】()にしよう
『カーテン』を新しい【カーテン】()にしよう
『照明』を新しい【照明】()にしよう
『テレビ』を新しい【テレビ】()にしよう
『アラーム』を新しい【アラーム】()にしよう
『外出ボタン』を新しい【外出ボタン】()にしよう

『管理人』を新しい【家の管理人】(『コーヒーメーカー』, 『カーテン』, 『照明』, 『テレビ』)にしよう
『テレビ』の〈つなぐ〉(『管理人』)を実行しよう
『アラーム』の〈つなぐ〉(『管理人』)を実行しよう
『外出ボタン』の〈つなぐ〉(『管理人』)を実行しよう

『アラーム』の〈鳴らす〉()を実行しよう
『テレビ』の〈つける〉()を実行しよう
『外出ボタン』の〈押す〉()を実行しよう

実行結果
アラーム: ジリリリ!
· コーヒーメーカー: コーヒーをいれます
· カーテン: 開きます
テレビ: つきました
· 照明: 明るさ 30
外出ボタン: 押されました
· テレビ: 消えました
· 照明: 明るさ 0
· カーテン: 閉まります

結果はリファクタリング前と1行も変わりません。でも今は、機器どうしの線がすべて消えて、家のルールは【家の管理人】1か所に集まっています。

ステップごとにもう一度

  • ステップ1 — 仲介者の約束。【仲介者】は〈知らせを受ける〉1つだけを約束します。機器は、相手が家の管理人なのかオフィスの管理人なのか知らなくても、この約束だけを信じて知らせます。
  • ステップ2 — 機器は仲介者だけを知っています。【機器】には、ほかの機器が1つもありません。『仲介者』1つと、そこに知らせる〈知らせる〉だけです。【テレビ】の〈つける〉は「テレビオン」を知らせるだけで、そのあと照明が暗くなることは知りません。
  • ステップ3 — ルールは仲介者に。【家の管理人】の〈知らせを受ける〉を見れば、家全体のルールがひと目で分かります。アラームが鳴ったら、テレビがついたら、外出したら、それぞれだれが何をするのかです。
  • ステップ4 — つなぐ。 機器を作ったあと、知らせる側の機器を同じ仲介者につなぎます。機器を作る時には何も渡しません。だから【アラーム】は、コーヒーメーカーのない家でもそのまま使えます。

コーヒーメーカーとカーテンも【機器】をもとにしました
この例では、コーヒーメーカー、カーテン、照明はまだ何も知らせません。それでも同じ骨組みを使っておけば、あとで「コーヒーができたら知らせる」のように知らせることができた時、〈知らせる〉をすぐに使えます。

仲介者のところには約束に従うものしかつなげないので、関係ないものをつなごうとすると、その場で止められます。

【仲介者】を規定しよう:
    〈知らせを受ける〉がなければならない(【文字列】の『出来事』)

【機器】を設計しよう:
    『仲介者』を空っぽに隠そう

    〈つなぐ〉を作ろう(【仲介者】の『仲介者』):
        『私』の『仲介者』を『仲介者』にしよう

『テレビ』を新しい【機器】()にしよう

(参考:仲介者の約束に従わないものには、つなげません)
『テレビ』の〈つなぐ〉(「リビングのリモコン」)を実行しよう

新しいルールを足してみると

「テレビをつけたらカーテンも閉める」ができたとしましょう。

  • 前のやり方:【テレビ】がカーテンを知らなければなりません。テレビのクラスに『カーテン』を足し、コンストラクターを直し、テレビを作る行でもカーテンを渡す必要があります。
  • メディエーターパターン:【家の管理人】の「テレビオン」の場合:に『私』の『カーテン』の〈閉める〉()を実行しようを1行足すだけで済みます。【テレビ】はそのままです。

やってみよう

  1. 上の「テレビをつけたらカーテンも閉める」を実際に足してみてください。
  2. 新しい機器【ロボット掃除機】を【機器】をもとにして作り、〈掃除する〉を持たせてみてください。外出ボタンを押したら掃除を始めるようにするには、ロボット掃除機のクラスと【家の管理人】だけを直せば済みます。【外出ボタン】は直す必要がありません。

どんな時に使うといいの?

  • オブジェクトどうしがたくさん呼び合って、網のようにからみ合っている時
  • オブジェクトを別の場所で使い回したいのに、ほかのオブジェクトがぞろぞろついてくる時
  • 「何かが起きたら、だれが何をするか」を1か所で見て直したい時
  • 画面の入力欄、ボタン、チェックボックスのように、お互いに影響し合う部品が多い時

いいところと気になるところ

いいところ 気になるところ
オブジェクトどうしがお互いを知らないので、切り離して使いやすいです ルールが全部集まるので、仲介者がだんだん大きく複雑になることがあります
ルールが1か所に集まって、ひと目で見えて直しやすいです 仲介者がすべてを知ることになり、仲介者を間違って直すと家全体がゆらぎます
新しいルールを足しても、機器はそのままです 1回通る分、だれがだれを動かしたのかをたどるのが少し遠くなります

早すぎるパターンは毒です。
機器が2つで、片方がもう片方を1回呼ぶくらいなら、直接呼ぶほうが単純です。線がからみ始めて、ルールを探すためにいくつものクラスを開くようになった時に、メディエーターを取り出しても遅くありません。

ほかのパターンとの関係

  • オブザーバーパターンと似ています。 どちらも「何かが起きたら知らせる」です。オブザーバーは知らせを受けた側が自分で何をするかを決め、メディエーターは仲介者がだれに何をさせるかを決めます。仲介者の中でオブザーバーを使って知らせを受けることもあります。
  • ファサードパターンとも似て見えます。 ファサードは、外から中のいくつものオブジェクトをかんたんに使えるように前に立つ窓口で、中のオブジェクトはファサードを知りません。メディエーターは、中のオブジェクトがお互いの代わりに仲介者と話すので、オブジェクトも仲介者を知っています。
  • コマンドパターンと一緒に使えます。 仲介者が機器にさせる仕事をコマンドオブジェクトにすれば、ルールを変えたり、記録して元に戻したりしやすくなります。
プレイグラウンド
結果
コードを書いて「実行」ボタンを押してください。