🎃
デザインパターンを学ぶ #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