判断ガイド: イベントを使うべきか・依存性注入を使うべきか・Singletonを許容できるか¶
イベント(Observer/Queue/Bus)を使うべきか¶
Q1. その処理は「ルールの帰結」か「観測」か「演出」か?([イベント通知の3分類](../11_case_studies/14_event_notification.md))
→ ルールの帰結(順序・確実性が仕様)──> イベントにしない。直接呼び出しで順序を固定
→ 観測(実績・統計・クエスト)──> Pub/Sub
→ 演出(UI・音)──> Event Queue(遅延+集約)
Q2. 反応者は複数か、増えるか?
→ 1つで固定 ──> コールバック1本か直接呼び出しで十分
Q3. 発行者は反応者を知り得る立場か?
→ 知っていて自然(同一画面の部品)──> [Mediator](../03_design_patterns/mediator.md)や直接参照
→ 知るべきでない(層をまたぐ)──> イベント
- 使わない場面: 因果を追いたいコアロジック、1フレームの遅延が致命的な判定、隣のクラスを呼ぶだけの場面
- トレードオフ: 疎結合 ⇔ 制御フローの不可視化(「なぜ起きた」が追えないコードは疎結合の失敗形)
依存性注入(DI)を使うべきか¶
Q1. その依存を差し替えたいか?(テスト・プラットフォーム・設定)
→ いいえ、永遠に1実装 ──> 直接使う(newも直書きでよい)
Q2. 差し替えたい → 注入方法を選ぶ
→ 依存が少数(〜4個)──> コンストラクタ手渡し(これもDI。コンテナ不要)
→ MonoBehaviour等でコンストラクタ不可 ──> SerializeField/初期化メソッド注入
→ オブジェクトグラフが巨大で手動配線が苦痛 ──> DIコンテナ(最後の手段)
Q3. コンストラクタ引数が6個を超えた?
→ 依存が多すぎるサイン。注入方法ではなく[クラスの分割](01_split_class.md)を先に検討
- 根拠: Service LocatorとDI、DIP
- トレードオフ: 依存の可視化・テスト容易性 ⇔ 配線コード。「DIコンテナ導入=DI」ではない(手渡しが基本形)
Singletonを許容できるか¶
Q1. 状態を持つか?
→ ほぼ無状態(ログ、数学、読み取り専用設定)──> 許容しやすい。namespaceの自由関数も検討
→ 可変状態を持つ(進行状況、プレイヤーデータ)──> Q2へ
Q2. その状態を読み書きする箇所は全域に散っているか?
→ 散らばる予定 ──> 危険信号。所有者を決めて引数/注入で配る設計に戻す
Q3. テストで差し替え・リセットが必要か?
→ 必要 ──> Singletonをやめるか、[Service Locator](../04_game_patterns/service_locator_and_di.md)(差し替え可能な中間形)へ
Q4. それでもSingletonにするなら:
・生成/破棄タイミングを明示(Startup/Shutdown方式)([初期化順問題](../07_memory_and_runtime/01_storage_duration_and_lifetime.md)の回避)
・書き込みAPIを絞る(読み取りは広く、書き込みは狭く)
・「なぜ許容したか」をコメントに残す
3つに共通する判断軸¶
いずれも「結合を見えなくする道具」(イベント=呼び出しを、DI=生成を、Singleton=依存を隠す)。隠してよいのは「知るべきでないもの」だけで、知るべきもの(順序・所有・依存)を隠すと必ず後で払う——これがこのページ全体の要約です。
理解度チェック¶
- 「ルールの帰結をイベントにしない」理由は?
- DIコンテナが「最後の手段」である理由は?
- Singleton許容の条件(状態・範囲・テスト)を言えますか。
前: 抽象化・パターンの判断 | カテゴリ目次 | 次: C++の選択