Mediator¶
解決する問題¶
多数のオブジェクトが互いに直接参照し合って通信すると、N個の部品にN×Nの結合が生まれる。相互作用を仲介役1つに集約し、各部品は仲介役だけを知る形にしたい。
登場人物と責務¶
- Mediator: 部品間の調整ロジックを一手に持つ(誰が変わったら誰を更新するか)
- Colleague(部品): 自分の変化をMediatorに通知するだけ。他の部品を知らない
最小構成図¶
悪い: 装備画面で [装備リスト]⇄[ステータス表示]⇄[プレビュー]⇄[比較パネル] が相互参照(N×N)
良い: [装備リスト] ─┐
[ステータス] ─┼──> EquipScreenMediator(調整はここだけ)
[プレビュー] ─┤
[比較パネル] ─┘
パターンなしの実装¶
// C++20(問題例)— UI部品が互いを直接知っている
class EquipListView {
public:
StatusPanel* status; // 生ポインタ多数: 誰が誰を所有しているかも曖昧
PreviewPane* preview;
ComparePanel* compare;
void OnSelect(const Item& item) {
status->ShowWith(item); // 選択が変わったら3つを自分で更新
preview->ShowModel(item);
compare->CompareWith(item);
}
};
// ComparePanelも選択に影響し…と続くと、部品追加のたび全部品を修正
問題点¶
- 部品追加・削除で他の全部品を修正(結合がN×Nで成長)
- 各部品が画面全体の仕様(誰を更新すべきか)を知ってしまい、単体で再利用できない
- 更新の連鎖(AがBを更新→BがCを…)で無限ループや順序バグ
パターン適用後のC++コード¶
// C++20
#include <string>
class EquipScreenMediator; // 前方宣言
class Widget { // Colleague基底
public:
explicit Widget(EquipScreenMediator& m) : mediator_(m) {}
virtual ~Widget() = default;
protected:
EquipScreenMediator& mediator_; // 仲介役だけを知る(参照: 画面が全体を所有)
};
class EquipListView : public Widget {
public:
using Widget::Widget;
void OnSelect(const Item& item); // 実装は下(Mediatorに伝えるだけ)
};
class StatusPanel : public Widget {
public:
using Widget::Widget;
void ShowWith(const Item& item) { /* 表示更新 */ }
};
class PreviewPane : public Widget {
public:
using Widget::Widget;
void ShowModel(const Item& item) { /* 3D表示 */ }
};
class EquipScreenMediator { // 画面の調整ロジックはここに凝集
public:
void OnItemSelected(const Item& item) {
status_->ShowWith(item);
preview_->ShowModel(item);
// 部品が増えてもこの1か所に足すだけ
}
// 部品の生成・所有・接続(省略)
private:
StatusPanel* status_ = nullptr; // 生ポインタ: Mediator(画面)が部品の寿命を管理
PreviewPane* preview_ = nullptr;
};
inline void EquipListView::OnSelect(const Item& item) {
mediator_.OnItemSelected(item); // 「選択された」と伝えるだけ。誰が反応するかは知らない
}
構造検証用サンプル: samples/pattern_mediator.cpp
C#またはUnityでの実装¶
// 画面(Presenter/Screenクラス)がMediatorになる形がUnity UIの定石
public class EquipScreen : MonoBehaviour {
[SerializeField] private EquipListView list;
[SerializeField] private StatusPanel status;
[SerializeField] private PreviewPane preview;
private void Awake() {
list.ItemSelected += OnItemSelected; // 部品→画面はevent(部品は画面を知らない)
}
private void OnItemSelected(Item item) { // 画面→部品は直接呼ぶ(画面は部品を知ってよい)
status.ShowWith(item);
preview.ShowModel(item);
}
}
「部品は上(画面)にイベントで報告、画面は下(部品)を直接制御」— この非対称が保守しやすいMediatorの形です(→ MVC/MVP/MVVMのPresenterはこの発展形)。
ゲームでの具体例¶
- 複雑なUI画面(装備、ショップ、スキルツリー): 部品間の連動を画面クラスに集約
- 対戦の審判(GameRules): プレイヤー・盤面・タイマーの相互作用を審判が調整
- 管制塔の比喩: 飛行機(部品)同士は交信せず、管制塔(Mediator)とだけ交信 — ただし比喩の限界として、実際のMediatorは「調整ロジックの置き場所」であり、通信の中継だけではない
利点¶
- 結合がN×NからN×1に減る。部品が画面仕様から独立し再利用可能に
- 相互作用のルールが1か所に集まり、「この画面で何が起きるか」が1ファイルで読める
- 更新の順序・ループ防止を1か所で制御できる
欠点¶
- Mediatorが神クラス化しやすい(全調整ロジックが集まるため必然的に大きくなる)。画面単位で分割し、肥大したら画面自体の分割を疑う
- 間接化により「ボタンを押すと何が起きるか」を追うのにMediator経由の1ホップが挟まる
- 部品→Mediatorの通知手段(イベント/インターフェース)の設計が別途必要
適用条件¶
- 部品同士の相互作用が多く(目安: 3部品以上が連動)、追加・変更が続く
- 部品を他の画面でも再利用したい
避けるべき条件¶
- 部品が2つで連動が単純 → 直接参照で十分
- 相互作用がない(単に並んでいるだけ)→ 調整役は不要
似たパターンとの違い¶
- Observer: Mediatorの実装部品としてよく使う(部品→Mediatorの通知)。Observerだけだと「誰が誰を購読しているか」が分散する。Mediatorは調整を中央集権にする
- Facade: 外→内の一方向の窓口 vs 内部部品同士の相互作用の調整
- Message Bus: 全域の疎結合通信。Mediatorは局所(1画面・1システム内)の密な調整。全部Busに流すと調整ロジックの置き場が消えて追跡不能になる
実務でよく見かける変形¶
- Presenter(MVP)/ Controller: UIにおけるMediatorの制度化
- GameMode/審判クラス: ルール調整のMediator(UEのGameModeもこの役割 → 第10部)
過剰設計になる例¶
ボタン2つの確認ダイアログにMediator+イベント+インターフェースの完全装備——OKボタンがダイアログを直接閉じてよいのです。
関連項目¶
理解度チェック¶
- Mediatorが減らす結合は「何から何へ」ですか。
- 「部品は上にイベント、画面は下に直接」の非対称が良い理由は?
- MediatorとMessage Busの使い分けの基準は?
演習¶
ショップ画面(商品リスト・所持金表示・購入ボタン・詳細パネル)の連動仕様を書き出し、Mediator方式で設計してください。「所持金不足なら購入ボタンを無効化」のロジックがどこに置かれるべきかに注目。