第1部 依存注入の役割
第1章:DIの基本 (The Basics of Dependency Injection)
著者はまず、DIに対する誤解(「DIコンテナを使うこと=DI」など)を解くところから始めます。
DIの真の定義
DIの定義
DIとは、疎結合(Loose Coupling)なコードを実現するための、ソフトウェア設計原則とパターンのセットである。
DIの目的は「疎結合」であり、その最終的なゴールはソフトウェアの保守性(Maintainability)を高めることです。
DIの4つの恩恵
なぜ疎結合にする必要があるのでしょうか?著者は以下の4点を挙げています。
- 遅延バインディング (Late Binding):実装クラスをコンパイル時ではなく、実行時(または構成時)に決定できる。これにより、コンパイルし直すことなく、一部のモジュール(例えばデータベースプロバイダ)を差し替えることが可能になります。
- 拡張性 (Extensibility):コードを書き換えずに、新しい機能を追加したり振る舞いを変更したりできる(開放閉鎖の原則)。
- 並行開発 (Parallel Development):インターフェースさえ決まれば、モジュールAとモジュールBを別のチームが同時に開発できる。
- 保守性 (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 の開発者はコードを動かすことができない。 |
オブジェクトグラフの問題
密結合なコードでは、Controller が Service を作り、Service が Repository を作る…というように、依存関係の解決が各クラス内に分散してしまいます。これを制御しようとすると、スパゲッティコードが生まれます。
第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.Now や new Random() は見落とされがちですが、これらもテストの再現性を破壊するため、DIの対象とすべきです。
結論:疎結合の実現
第3章の結論として、疎結合を実現するためには以下のステップが必要であると説いています。
- Volatile Dependency を特定する。
- それらをインターフェース(抽象)の背後に隠す。
- 具象クラスではなく、インターフェースを利用するようコードを書き換える。
第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:2px2. 注入パターンの選択ガイド
「どの注入方法を使えばいいのか?」という疑問に対し、本書は明確な基準を設けています。
| パターン名 | 概要 | 採用基準 (いつ使うか) | メリット / デメリット |
| 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();
}
}
なぜこれが悪なのか?
- APIが嘘をつく: コンストラクタが空なので、外から見て「このクラスが何に依存しているか」が分からない。
- 実行時エラー: コンパイルは通るが、実行時に
Resolveが失敗して落ちる可能性がある。 - 保守困難: 依存関係が見えなくなり、スパゲッティコード化する。
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解決策:
- 設計を見直す(推奨): 共通部分を
Class Cに切り出し、AとBが共にCに依存するようにする。 - イベントやデリゲートを使う: 直接参照を断ち切る。
- 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_- Register (登録): どのインターフェースがどのクラスにマッピングされるかを設定する。
- Resolve (解決): 必要なオブジェクトグラフ(依存関係のツリー)を生成する。
- 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 -.-> noteDIコンテナを使えば、設定ひとつで「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 など)があります。どれを選ぶべきでしょうか?
コンテナ選定の基準
著者は、「基本的な機能(登録・解決・ライフサイクル)」はどのコンテナも大差ないとしています。
差がつくのは以下のポイントです。
- 診断能力 (Diagnostics):
- 設定ミスやCaptive Dependency(第8章参照)を、実行前や起動時に検出してくれるか?
- ここが貧弱なコンテナを使うと、本番環境で「謎のメモリエラー」に苦しむことになる。
- パフォーマンス:
- Webアプリのようにリクエスト数が多い場合、Resolveの速度は重要。
- 純粋性 (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(インターセプション)を活用した堅牢な設計を目指すなら、標準コンテナを捨て、Autofac や Simple Injector などのサードパーティ製コンテナに置き換えるべきである。
3. ASP.NET Core におけるライフサイクル
Webアプリは「リクエスト」が基本単位です。
| ライフサイクル | Webアプリでの意味 | 注意点 |
| Scoped | 1リクエスト = 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.csのOnStartupメソッド
多くの開発者が、個々の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を注入し、それ経由で生成する。

コメント