🎃

デザインパターンを学ぶ #22 メディエーター(Mediator)

に公開

1. はじめに

メディエーターは、複数オブジェクト間のやり取りを調停役に一元化し、相互依存のスパゲッティ化を防ぐパターン。UIコンポーネントやワークフローの連動ロジックを「1か所」に集めたいときに使う。

2. Mediatorとは?

登場役は3つ:

  • Mediator … 通知を受けて他のコンポーネントへ指示する窓口
  • ConcreteMediator … 実際の連動ロジックを実装
  • Colleague(同僚) … 直接は話さず、必ず Mediator 経由でやり取り

ポイント:多対多の依存(コンポーネント同士が互いに参照)を、**星型(一対多)**に変換する。連動ルールの変更は Mediator だけ直せばよい。

3. 実装イメージ(PHP)

検索ダイアログの例。TextBox が空なら Button を無効、チェックボックスで検索モードを切り替える連動を Mediator に集約。

<?php
// 1) 基本インターフェース
interface Mediator {
    public function notify(Component $sender, string $event): void;
}

abstract class Component {
    protected Mediator $mediator;
    public function setMediator(Mediator $m): void { $this->mediator = $m; }
}

// 2) 具体コンポーネント
class TextBox extends Component {
    private string $text = '';
    public function setText(string $t): void { $this->text = $t; $this->mediator->notify($this, 'text_changed'); }
    public function getText(): string { return $this->text; }
}

class CheckBox extends Component {
    private bool $checked = false;
    public function toggle(): void { $this->checked = !$this->checked; $this->mediator->notify($this, 'toggled'); }
    public function isChecked(): bool { return $this->checked; }
}

class Button extends Component {
    private bool $enabled = false;
    public function setEnabled(bool $e): void { $this->enabled = $e; }
    public function click(): void {
        if (!$this->enabled) { echo "[Button] disabled\n"; return; }
        $this->mediator->notify($this, 'clicked');
    }
    public function isEnabled(): bool { return $this->enabled; }
}

// 3) 具体メディエーター(連動ロジックの集約)
class SearchDialog implements Mediator {
    public TextBox $query;
    public CheckBox $exact;
    public Button $search;

    public function __construct() {
        $this->query = new TextBox();
        $this->exact = new CheckBox();
        $this->search = new Button();

        $this->query->setMediator($this);
        $this->exact->setMediator($this);
        $this->search->setMediator($this);

        $this->recompute();
    }

    private function recompute(): void {
        // ルール例:クエリが空なら検索不可
        $this->search->setEnabled($this->query->getText() !== '');
    }

    public function notify(Component $sender, string $event): void {
        if ($sender === $this->query && $event === 'text_changed') {
            $this->recompute();
        }
        if ($sender === $this->exact && $event === 'toggled') {
            // 例:厳密検索モードの切替に応じて何か調整(ログ表示だけ)
            echo "[Mediator] exact mode: " . ($this->exact->isChecked() ? 'ON' : 'OFF') . PHP_EOL;
        }
        if ($sender === $this->search && $event === 'clicked') {
            $mode = $this->exact->isChecked() ? 'EXACT' : 'FUZZY';
            echo "[Mediator] search '{$this->query->getText()}' mode={$mode}" . PHP_EOL;
        }
    }
}

// --- 実行例 ---
$dlg = new SearchDialog();
$dlg->search->click();               // disabled
$dlg->query->setText('php mediator');
$dlg->search->click();               // 実行
$dlg->exact->toggle();               // mode切替
$dlg->search->click();               // EXACTで実行

4. メリット・デメリット

メリット

  • 連動ロジックの散逸を防止(変更点を1か所に集約)
  • コンポーネント同士の結合度を低減(単体テストしやすい)
  • 新しいコンポーネント追加もMediator側の調整中心で済む

デメリット

  • Mediator が太りやすくゴッドオブジェクト化のリスク
  • 連動が多い画面では設計の粗さが直撃(責務分割が必要)

5. 使いどころ

  • フォーム/ダイアログで複数入力の相互依存が多い(入力AでB/Cの活性や値が変わる)
  • ワークフロー/業務フローの状態遷移で、関係者オブジェクトの通知を一元化したい
  • チャットルームのような多参加者のやり取りをハブで調停したい

「コンポーネント間の会話が増えてつらい」と感じたら、まずメディエーターで会話の窓口を1つに

Discussion