MENU

DIの原理・原則とパターン

第1部 依存注入の役割

目次

第1章:DIの基本 (The Basics of Dependency Injection)

著者はまず、DIに対する誤解(「DIコンテナを使うこと=DI」など)を解くところから始めます。

DIの真の定義

DIの定義

DIとは、疎結合(Loose Coupling)なコードを実現するための、ソフトウェア設計原則とパターンのセットである。

DIの目的は「疎結合」であり、その最終的なゴールはソフトウェアの保守性(Maintainability)を高めることです。

DIの4つの恩恵

なぜ疎結合にする必要があるのでしょうか?著者は以下の4点を挙げています。

  1. 遅延バインディング (Late Binding):実装クラスをコンパイル時ではなく、実行時(または構成時)に決定できる。これにより、コンパイルし直すことなく、一部のモジュール(例えばデータベースプロバイダ)を差し替えることが可能になります。
  2. 拡張性 (Extensibility):コードを書き換えずに、新しい機能を追加したり振る舞いを変更したりできる(開放閉鎖の原則)。
  3. 並行開発 (Parallel Development):インターフェースさえ決まれば、モジュールAとモジュールBを別のチームが同時に開発できる。
  4. 保守性 (Maintainability):責務が明確に分離されているため、修正の影響範囲が限定され、テストが容易になる。

第2章:密結合なコード (Tightly Coupled Code)

第2章では、「DIを使わない世界(密結合)」がどのような問題を引き起こすかを具体的に解説しています。

密結合の正体

密結合とは、あるクラスが依存するクラスを直接生成(new)している状態です。

// ❌ 密結合の例
public class OrderService
{
    public void SaveOrder(Order order)
    {
        // ここで具体的なSQLリポジトリを直接newしていることが諸悪の根源
        var repo = new SqlOrderRepository(); 
        repo.Save(order);
    }
}

このたった1行の new が、以下の致命的な問題を引き起こします。

問題点解説
拡張性の欠如データをCSVファイルに保存したくなっても、OrderService のコードを書き換えない限り変更できない。
テスト不能ユニットテストを実行するたびに本物のデータベースに接続してしまうため、セットアップが困難で実行速度も遅くなる。
並行開発の阻害SqlOrderRepository が完成するまで、OrderService の開発者はコードを動かすことができない。

オブジェクトグラフの問題

密結合なコードでは、ControllerService を作り、ServiceRepository を作る…というように、依存関係の解決が各クラス内に分散してしまいます。これを制御しようとすると、スパゲッティコードが生まれます。


第3章:疎結合なコード (Loosely Coupled Code)

第3章は本書のハイライトの一つであり、エンジニアが最も意識すべき「依存の選別」が語られます。

制御の反転 (Inversion of Control)

密結合を解消するために、「誰が依存インスタンスを用意するか」という制御を逆転させます。クラスは「私はこれが必要です」と宣言するだけで、自分で new しません。

ここで重要なのは、「インターフェースに対してプログラムする」ことです。

flowchart LR
    subgraph Tight["密結合 (依存の方向と制御が同じ)"]
        A[Consumer] -- new --> B[Concrete Implementation]
    end
    
    subgraph Loose["疎結合 (DIによる制御の反転)"]
        X[Consumer] -- 依存 --> I[Interface]
        Y[Concrete Implementation] -. 実装 .-> I
        Injector[???] -- 実装を渡す --> X
    end

【重要】依存の分類:Volatile vs Stable

ここが第3章の核心です。「すべての new を排除すべきか?」というと、そうではありません。著者は依存関係を明確に2つに分類し、DIすべきものしなくてよいものを定義しています。

依存の種類定義・特徴具体例アクション
Stable Dependency
(安定依存)
・すでに存在している
・決定的な動作をする(常に同じ結果)
・新しいバージョンでも後方互換性がある
・独自のアルゴリズムを含まない
string, int などのプリミティブ型
・BCL (Base Class Library) の多く
・DTO (データ転送オブジェクト)
DI不要
(new してOK)
Volatile Dependency
(不安定依存)
・開発環境やすべてのマシンに存在するとは限らない
・非決定的な動作(実行ごとに結果が変わる)
・開発中である
・外部リソースにアクセスする
データベース
ファイルシステム
Web API
システム時計 (DateTime.Now)
乱数生成
必ずDIする
(インターフェース経由)

エンジニアが犯しやすいミスは、Volatile Dependency(不安定依存)をクラス内で直接生成してしまうことです。特に DateTime.Nownew Random() は見落とされがちですが、これらもテストの再現性を破壊するため、DIの対象とすべきです。

結論:疎結合の実現

第3章の結論として、疎結合を実現するためには以下のステップが必要であると説いています。

  1. Volatile Dependency を特定する。
  2. それらをインターフェース(抽象)の背後に隠す。
  3. 具象クラスではなく、インターフェースを利用するようコードを書き換える。

第2部 カタログ

第1部で「なぜDIが必要か」を学びました。第2部は「カタログ(Catalog)」です。 ここでは、「正しいDIの実装方法(パターン)」、「絶対にやってはいけない実装(アンチパターン)」、そして「設計の警告サイン(コードの臭い)」が体系化されています。

第4章:DIパターン (DI Patterns)

依存を注入する方法は、実は数種類しかありません。著者はこれらを明確に定義し、優先順位をつけています。

1. Composition Root(コンポジション・ルート)

本書で最も重要なパターンです。これを知らないエンジニアは、DIコンテナを間違った方法(Service Locator)で使いがちです。

定義 アプリケーションのエントリーポイント(Mainメソッド、Web APIのStartupなど)に限りなく近い唯一の場所で、モジュールを構成(Compose)する。

  • やってはいけないこと: アプリケーションのあちこちでDIコンテナを参照する。
  • やるべきこと: 起動時に一回だけ、全てのオブジェクトグラフを一気に組み立てる。
flowchart TD
    subgraph AppEntry ["Entry Point"]
        Main[Main Method / Startup]
    end
    
    subgraph CR ["Composition Root"]
        Composer[Object Graph Composer]
        note["ここで new A(new B(new C())) のように<br>全てを組み立てる"]
    end
    
    subgraph AppLogic ["Application Logic"]
        Controllers
        Services
        Repositories
    end
    
    Main --> Composer
    Composer --> Controllers
    Composer --> Services
    Composer --> Repositories
    
    style CR fill:#f9f,stroke:#333,stroke-width:2px

2. 注入パターンの選択ガイド

「どの注入方法を使えばいいのか?」という疑問に対し、本書は明確な基準を設けています。

パターン名概要採用基準 (いつ使うか)メリット / デメリット
Constructor Injection
(コンストラクタ注入)
コンストラクタ引数で依存を受け取る基本はこれ一択。
必須の依存関係すべてに使用。
不変性: クラス生成時に完全な状態であることを保証。
透明性: 何が必要か一目瞭然。
⚠️ 引数が多くなりがち(ただしそれは設計の警告)。
Property Injection
(プロパティ注入)
publicプロパティに値をセットするオプショナルな依存のみ。
(例: あれば使うロガー、なければデフォルト動作)
✅ 柔軟。
⚠️ 一時的結合: セットし忘れると実行時エラー(NullReference)になるリスク。
Method Injection
(メソッド注入)
メソッドの引数で依存を受け取る呼び出しごとに依存が変わる場合
(例: userContext など)
✅ 呼び出し側(Consumer)が制御できる。
⚠️ メソッドのシグネチャが汚れる。

鉄則

デフォルトでは常にConstructor Injectionを選択せよ。依存関係は隠さず、最初から明らかにするべきである。

第5章:DIアンチパターン (DI Anti-Patterns)

第5章は「やってはいけないリスト」です。これらは一見便利に見えますが、長期的にはコードベースを破壊します。

1. Service Locator(サービスロケーター)

これは「アンチパターンの王」として徹底的に批判されています。

// ❌ 絶対にやってはいけないコード
public class OrderService
{
    public void PlaceOrder()
    {
        // 必要なものを「問い合わせ」ている
        var repo = Container.Resolve<IOrderRepository>();
        repo.Save();
    }
}

なぜこれが悪なのか?

  1. APIが嘘をつく: コンストラクタが空なので、外から見て「このクラスが何に依存しているか」が分からない。
  2. 実行時エラー: コンパイルは通るが、実行時に Resolve が失敗して落ちる可能性がある。
  3. 保守困難: 依存関係が見えなくなり、スパゲッティコード化する。

2. Control Freak(コントロール・フリーク)

依存オブジェクトをクラス内で直接 new してしまうこと(第1部で触れた密結合のこと)。特に new keyword を使って Volatile Dependency(DB接続など)を生成するのは厳禁です。

3. Ambient Context(アンビエント・コンテキスト)

静的プロパティ(Static)を使って、グローバルにアクセスできるコンテキストを作ることです。

  • 例: HttpContext.Current, DateTime.Now, カスタムな MyApp.CurrentLog
  • 問題点: テスト時に挙動を差し替えるのが極めて困難。グローバル変数の亜種であり、副作用を生みやすい。

4. Constrained Construction(制約付き構築)

「プラグインを作るためには、必ず引数なしのコンストラクタを持たなければならない」といった制約を課すこと。これはDIの柔軟性を殺します。

第6章:コードの臭い (Code Smells)

正しくConstructor Injectionを使っていても、「何かおかしい」と感じることがあります。第6章では、DIに起因するように見える問題が、実はクラス設計の問題であることを指摘しています。

1. Constructor Over-injection(コンストラクタの肥大化)

「コンストラクタの引数が12個もある!DIは見にくい!」という不満。

  • 真の原因: DIのせいではなく、そのクラスが単一責任の原則(SRP)に違反している(やりすぎている)からです。
  • 解決策:
    • クラスを分割する。
    • 関連する依存をまとめて Facade Service(ファサード) を作る。

2. Abuse of Abstract Factories(抽象ファクトリの乱用)

依存関係を取得するために、何でもかんでも IServiceFactory のようなファクトリを注入すること。

  • 問題点: コードが複雑になり、Service Locatorに近づいてしまう。
  • 解決策: オブジェクトの生成が必要な場合以外は、インスタンスそのものを注入する。

3. Cyclic Dependencies(循環参照)

AがBに依存し、BがAに依存している状態。DIコンテナはこれを解決できず、スタックオーバーフローを起こします。

graph LR
    A[Class A] --> B[Class B]
    B --> A
    style A fill:#ffcccc
    style B fill:#ffcccc

解決策:

  1. 設計を見直す(推奨): 共通部分を Class C に切り出し、AとBが共にCに依存するようにする。
  2. イベントやデリゲートを使う: 直接参照を断ち切る。
  3. Property Injectionを使う: どちらか一方をプロパティ注入にしてサイクルを回避する(最終手段)。

第3部:DIコンテナ

第3部のテーマは「自動化」です。しかし、著者は「まずはPure DI(コンテナを使わない手動DI)から始めよ」と繰り返しています。その上で、アプリケーションが複雑化した時に、DIコンテナがどのように役立つか、そしてどのように制御すべきかを解き明かします。

第7章:DIコンテナの導入 (Introduction to DI Containers)

この章では、DIコンテナとは何か、そしてそれを使うべきタイミングについて定義しています。

DIコンテナとは何か?

DIコンテナは、Composition Root(組立場所)での作業を自動化するためのライブラリです。

数百のクラスがある大規模なアプリでは、手動で new Controller(new Service(new Repository...)) と書くのが大変になります。DIコンテナは、オートワイヤリング(Auto-wiring)という機能を使って、コンストラクタの型情報から自動的に依存関係を解決してくれます。

Pure DI vs DIコンテナ

著者は、常にDIコンテナを使うべきとは言っていません。以下の表を基準に判断すべきです。

特徴Pure DI (手動DI)DI Container (ツール利用)
コンパイル時安全性あり
依存関係の不一致があればビルドエラーになる。
なし
設定ミスがあってもビルドは通るが、実行時にエラーになる。
可読性高い
new が見えるので追跡容易。
低い
魔法のように動くため、裏で何が起きているか見えにくい。
記述量多い(メンテナンスコスト増)。少ない(設定ルールを書くだけ)。
学習コスト不要。必要(ライブラリごとの癖がある)。

著者の推奨

小〜中規模のプロジェクト、あるいは学習段階では Pure DI を推奨する。DIコンテナは、Pure DIのメンテナンスコストがメリットを上回った時に初めて導入すべきである。

RRRパターン(Register, Resolve, Release)

すべてのDIコンテナは、共通して以下の3つのフェーズで動作します。

flowchart LR
    subgraph config ["Register (設定)"]
        direction TB
        Code["コードでの設定"]
        Convention["規約ベース(Auto-Scan)"]
    end

    subgraph run ["Resolve (解決)"]
        Request["オブジェクト要求"]
        Graph["グラフ構築"]
    end

    subgraph end_ ["Release (解放)"]
        Dispose["IDisposable.Dispose()"]
        GC["ガベージコレクション"]
    end

    config --> run
    run --> end_
  1. Register (登録): どのインターフェースがどのクラスにマッピングされるかを設定する。
  2. Resolve (解決): 必要なオブジェクトグラフ(依存関係のツリー)を生成する。
  3. Release (解放): 使い終わったオブジェクト(特に IDisposable)を適切に破棄する。

第8章:オブジェクトのライフサイクル (Object Lifetime)

★ここが第3部の最重要パートです。 DIコンテナを使う最大のメリットは、「オブジェクト生成」と「破棄」の管理をコンテナに任せられる点にあります。しかし、設定を間違えるとメモリリーク並行処理バグを引き起こします。

主要な3つのライフスタイル

多くのDIコンテナには共通して3つのライフスタイル(生存期間)があります。これを適切に使い分けることがエンジニアの責務です。

ライフスタイル動作向いているオブジェクト注意点
Transient
(トランジェント)
要求されるたびに毎回新しいインスタンスを作成する。ステートレスなサービス、軽いオブジェクト。(デフォルトはこれにする)生成コストが高いオブジェクトには向かない。
Scoped
(スコープド)
1つのリクエスト(スコープ)内で1つのインスタンスを使い回す。
Webリクエスト終了時に破棄される。
DbContext (Entity Framework)、ユーザーセッション、Unit of Work。Webアプリ開発で最も重要。スレッド間で共有されないので安全。
Singleton
(シングルトン)
アプリケーションが終了するまで永遠に1つのインスタンスを使い回す。キャッシュ、設定値読み込みサービス、スレッドセーフなシングルトン。スレッドセーフでなければならない。 状態(State)を持つとバグの温床になる。

【危険】Captive Dependency(捕捉された依存関係)

これはDIコンテナ初心者が必ず踏む地雷です。

「寿命の長いオブジェクト」が「寿命の短いオブジェクト」を保持してしまう現象を指します。

  • シナリオ: Singleton のサービスが、Transient(または Scoped)のコンポーネントをコンストラクタで受け取った場合。
  • 結果: 本来すぐに破棄されるはずの Transient オブジェクトが、Singleton に掴まれたまま永遠にメモリに残り続けます。
    • DB Contextの場合: コネクションが閉じられず、システムがクラッシュしたり、古いデータを返し続けたりします。
flowchart TD
    subgraph Safe ["✅ 安全な依存"]
        Trans1[Transient] --> Sing1[Singleton]
        Scope1[Scoped] --> Sing2[Singleton]
        note1[短い寿命のものは<br>長い寿命のものに依存してOK]
    end

    subgraph Danger ["❌ Captive Dependency (危険)"]
        SingX[Singleton Service] -- 保持 --> TransX[Transient Component]
        SingY[Singleton Service] -- 保持 --> ScopeY[Scoped DB Context]
        
        note2[長い寿命のものが<br>短い寿命のものを保持すると<br>寿命が意図せず伸びてしまう!]
    end
    
    style Danger fill:#ffe6e6,stroke:#ff0000

警告 シングルトンの中に DbContext(通常はScoped)を注入してはならない。これは致命的なバグを引き起こす。

オブジェクトの解放 (Release / Disposal)

Pure DIの場合、IDisposable を実装したオブジェクトを使うときは、開発者が責任を持って Dispose() を呼ばなければなりません。

  • Pure DIの欠点: 依存関係が深くなると、誰が誰をDisposeすべきか管理が困難になる。
  • DIコンテナの利点: コンテナは「自分が作ったDisposableなオブジェクト」をリストアップしており、スコープ終了時(リクエスト終了時など)に自動的にDisposeを呼んでくれます。これがDIコンテナを使う大きな動機の一つです。

第9章:インターセプション (Interception)

この章は、オブジェクト指向設計における「魔法」のようなテクニック、Decoratorパターンの応用についてです。

横断的関心事 (Cross-Cutting Concerns)

ビジネスロジックを書いていると、本質的ではないが「やらなければならないこと」がたくさん出てきます。

  • ログ出力
  • 監査(Auditing)
  • パフォーマンス計測
  • トランザクション管理
  • エラーハンドリング / リトライ処理(Circuit Breaker)
  • キャッシュ
  • セキュリティ(権限チェック)

これらをビジネスロジックの中に直接書いてしまうと、コードは以下のように汚染されます。

// ❌ インターセプションを使わない場合
public void UpdateProduct(Product p)
{
    _logger.Log("Start update..."); // ノイズ
    if (!_auth.IsAdmin()) throw new UnauthorizedException(); // ノイズ
    
    // やっとビジネスロジック...
    _repo.Update(p);

    _logger.Log("End update..."); // ノイズ
}

Decoratorパターンによる解決

著者は、これらの関心事をインターセプション(介入)によって解決することを推奨しています。 具体的には、Decoratorパターンを使って、既存のクラスをラップし、外側から機能を追加します。

graph LR
    Client -- "呼び出し" --> Log["Logging Decorator"]
    Log -- "転送" --> Cache["Caching Decorator"]
    Cache -- "転送" --> Real["Real Service (Business Logic)"]
    
    note["ビジネスロジックは<br>ログやキャッシュのことを<br>一切知らない!"]
    Real -.-> note

DIコンテナを使えば、設定ひとつで「IProductService を要求されたら、LoggingDecorator で包んだ ProductService を返す」といった構成が容易に実現できます。

メリット

開放閉鎖の原則(OCP)を極限まで満たすことができる。既存のコード(Real Service)を一行も修正することなく、新しい機能(ログやキャッシュ)を追加できるからだ。


第10章:アスペクト指向プログラミング (Aspect-Oriented Programming)

第9章の概念をさらに広げ、システム全体にインターセプションを適用する手法(AOP)について議論します。

インターセプションの2つのアプローチ

DIにおけるAOPの実装には、大きく分けて2つの流派があります。著者は明確に片方を支持しています。

アプローチ技術的な仕組みメリットデメリット著者の推奨
Decorator
(コンパイル時)
標準的なC#のクラスとしてデコレータを作成し、コンストラクタ注入でラップする。・コンパイラが検証してくれる(型安全)。
・魔法がなく、読みやすい。
・デバッグが容易。
・似たようなデコレータをたくさん書く必要があるかもしれない。推奨 (Strongly Recommended)
Dynamic Interception
(実行時)
Castle DynamicProxy などのツールを使い、実行時に動的にプロキシクラスを生成する。・コードを書く量が減る(汎用的なインターセプタを1つ書けば全クラスに適用可能)。・動作が魔法めいており、ブラックボックス化する。
・実行速度がわずかに落ちる。
・デバッグが困難。
非推奨 (Use with caution)

アスペクトとしての機能

著者は、「トランザクション管理」や「リトライ処理(Circuit Breaker)」などを、ビジネスロジックから完全に切り離し、アスペクト(側面)として定義することを強く勧めています。

例えば、「データベースの接続が切れたら3回リトライする」というロジックは、すべてのRepositoryに書くのではなく、たった1つの RetryDecorator クラスを作成し、DIコンテナですべてのRepositoryに適用すれば完了です。


第11章:DIコンテナの選定 (Choosing a DI Container)

市場には多くのDIコンテナ(Autofac, Ninject, Simple Injector, Microsoft.Extensions.DependencyInjection など)があります。どれを選ぶべきでしょうか?

コンテナ選定の基準

著者は、「基本的な機能(登録・解決・ライフサイクル)」はどのコンテナも大差ないとしています。

差がつくのは以下のポイントです。

  1. 診断能力 (Diagnostics):
    • 設定ミスやCaptive Dependency(第8章参照)を、実行前や起動時に検出してくれるか?
    • ここが貧弱なコンテナを使うと、本番環境で「謎のメモリエラー」に苦しむことになる。
  2. パフォーマンス:
    • Webアプリのようにリクエスト数が多い場合、Resolveの速度は重要。
  3. 純粋性 (Pureness):
    • そのコンテナを使うために、アプリケーションコードに独自の属性([Inject] など)をつける必要があるか?
    • Conformist Container(順応主義のコンテナ)は避けるべき。コードがコンテナに依存してしまうため。

著者のスタンス:アダプターの使用

どのコンテナを選んだとしても、アプリケーションコードが特定のコンテナに依存してはいけません。

Container.Resolve<T>() をコードのあちこちに書くのは論外です。

理想的には、Composition Root(Startup.csなど)以外では、DIコンテナの存在すら知らない状態を作るべきです。


第4部:実戦でのDI

このパートのテーマは「フレームワークとの戦い」です。 多くのフレームワークは独自の「お作法」を持っており、時としてDIの原則と対立します。エンジニアは、フレームワークの制約の中で、いかにしてきれいなDIパターン(特にComposition Root)を守り抜くかが試されます。

第12章:ASP.NET Core におけるDI (DI in ASP.NET Core)

ASP.NET Coreは、歴史上初めて「DIが前提として組み込まれた」マイクロソフトのWebフレームワークです。しかし、著者は手放しでこれを称賛しているわけではありません。そこには大きな落とし穴があります。

1. 組み込みDIコンテナ (MS.Extensions.DependencyInjection)

ASP.NET Coreはデフォルトで軽量なDIコンテナを持っています。

Startup.cs (または Program.cs) で services.AddTransient<...>() のように登録するのは、もはや.NETエンジニアの常識となりました。

著者の評価:

  • Good: DIが「当たり前のもの」として普及したことは素晴らしい。
  • Bad: 機能が貧弱であり、Conforming Container(適合コンテナ)という問題を引き起こしている。

2. Conforming Container (適合コンテナ) の罠

これが第12章の最大の警鐘です。

マイクロソフトは、あらゆるDIライブラリ(Autofac, Ninjectなど)が共通して使える「最小公倍数」的な抽象化(IServiceCollection)を提供しました。

  • 問題点: 「全てのコンテナで動く」ようにするために、最も機能が低いコンテナ(組み込みコンテナ)に合わせて仕様が制限されている
  • 具体例: 高度な機能(第9章で学んだDecoratorパターンの自動登録や、複雑なインターセプション)が、標準のAPIではサポートされていない。

戦略的アドバイス

単純なCRUDアプリなら標準の組み込みコンテナで十分である。

しかし、AOP(インターセプション)を活用した堅牢な設計を目指すなら、標準コンテナを捨て、AutofacSimple Injector などのサードパーティ製コンテナに置き換えるべきである。

3. ASP.NET Core におけるライフサイクル

Webアプリは「リクエスト」が基本単位です。

ライフサイクルWebアプリでの意味注意点
Scoped1リクエスト = 1インスタンス
HTTPリクエストが来てからレスポンスを返すまで生存。
DbContext はこれ。最も安全でよく使う。
Transient注入されるたびに生成。軽いサービス向け。
Singletonアプリ起動中ずっと生存。絶対に Scoped なオブジェクト(DbContext)を持ってはいけない(Captive Dependency)。

4. Middlewareへの注入

Controllerだけでなく、MiddlewareもDIの対象です。標準のDIはコンストラクタ注入を推奨していますが、Middlewareは少し特殊で、Invoke メソッドでのメソッド注入(Method Injection)を活用することが多いです。


第13章:UWP/WPF におけるDI (DI in Universal Windows Platform / Desktop)

第13章の舞台は、Webから離れてデスクトップアプリケーション(WPF, UWP, Xamarin/MAUI)に移ります。

ここはWebとは全く異なる戦略が必要です。なぜなら、Webは「リクエストが終われば消える(Stateless)」のに対し、デスクトップアプリは「ユーザーが閉じるまで状態が続く(Stateful)」からです。

1. Composition Root の場所

Webアプリでは Startup クラスがありましたが、デスクトップアプリではどこでオブジェクトを組み立てるべきでしょうか?

正解: App.xaml.csOnStartup メソッド

多くの開発者が、個々のWindow(View)の中でViewModelを new してしまいますが、それは密結合です。アプリケーション起動時に、メインウィンドウとその依存関係をすべて構成する必要があります。

2. ViewModel の注入

MVVMパターンを採用する場合、ViewModelのコンストラクタに依存(ServiceやRepository)を注入します。View(XAML)はViewModelを受け取るだけにします。

// 正しいComposition Rootの例(WPF)
protected override void OnStartup(StartupEventArgs e)
{
    // ここで一気にグラフを作る
    var repo = new SqlDataRepository(connectionString);
    var viewModel = new MainViewModel(repo); // 依存を注入
    var window = new MainWindow(viewModel); // ViewModelを渡す
    
    window.Show();
}

3. 「新しいウィンドウ」問題と抽象ファクトリ

デスクトップアプリ特有の難問があります。

**「ユーザーがボタンを押した時に、新しい子ウィンドウを開く」**場合、どうやってDIすればいいのでしょうか? コンストラクタで「未来のウィンドウ」を受け取ることはできません。

ここでAbstract Factory パターンが登場します。

graph LR
    User -- "ボタンクリック" --> MainVM["Main ViewModel"]
    MainVM -- "Create()" --> Factory["IChildWindowFactory<br>(Abstract Factory)"]
    Factory -- "Resolve" --> Container["DI Container / Root"]
    Container -- "生成(依存注入付き)" --> ChildVM["Child ViewModel"]
    ChildVM --> ChildWindow["Child Window"]
    
    note["ViewModelの中で new ChildWindow() <br>をしてはいけない!<br>ファクトリに作らせる。"]
    MainVM -.-> note
  • やってはいけない: MainViewModel の中で new ChildWindow() を書く(DIが途切れる)。
  • やるべき: IChildWindowFactory を注入し、それ経由で生成する。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次