コンテンツにスキップ

判断ガイド: イベントを使うべきか・依存性注入を使うべきか・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とDIDIP
  • トレードオフ: 依存の可視化・テスト容易性 ⇔ 配線コード。「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を絞る(読み取りは広く、書き込みは狭く)
    ・「なぜ許容したか」をコメントに残す
  • 根拠: Singleton。UEならSubsystemという公式の中間解がある
  • トレードオフ: アクセスの手軽さ ⇔ 隠れ依存・テスト困難・神クラス化。「便利」は総コストの前借り

3つに共通する判断軸

いずれも「結合を見えなくする道具」(イベント=呼び出しを、DI=生成を、Singleton=依存を隠す)。隠してよいのは「知るべきでないもの」だけで、知るべきもの(順序・所有・依存)を隠すと必ず後で払う——これがこのページ全体の要約です。

理解度チェック

  1. 「ルールの帰結をイベントにしない」理由は?
  2. DIコンテナが「最後の手段」である理由は?
  3. Singleton許容の条件(状態・範囲・テスト)を言えますか。

前: 抽象化・パターンの判断 | カテゴリ目次 | 次: C++の選択