#0135（2025/05/14）依存性の注入（Dependency Injection：DI）を理解する

## 依存性の注入（Dependency Injection：DI）を理解する

---

### 1. 依存性の注入とは何か？

ソフトウェアを構成するクラスやモジュールは、たいてい他のクラスやモジュール（= 依存性）に頼って処理を行います。**依存性の注入**とは、依存先を「自分で new して確保する」のではなく、**外部から渡して（inject して）もらう設計手法**です。これにより、コンポーネント同士の結合度が下がり、「疎結合・高凝集」な設計を実現できます。

> **コーヒーメーカーのたとえ**：コーヒーメーカー本体が自分でフィルターを買いに走るのではなく、バリスタ（外部）がフィルターや豆をセットしてあげるイメージ。

#### 主なメリット

* **テスト容易性**：本物の依存先をモックへ差し替えられ、ユニットテストが高速で安定。
* **柔軟性 / 拡張性**：実装を差し替えるだけで機能追加や改善が可能。
* **再利用性**：依存の詳細に縛られないため、コードを他プロジェクトへ持ち込みやすい。

---

### 2. Angular における DI の仕組み

Angular は **DI コンテナ**をフレームワーク内部に持ち、サービスや値を自動で解決・供給します。

#### 2‑1. サービス定義と `@Injectable`

```ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' }) // アプリ全体で単一インスタンス
export class AuthService {
  private token = '';

  setToken(t: string) { this.token = t; }
  getToken() { return this.token; }
}
```

* `providedIn: 'root'` を付けることで **Singleton** として登録、`AppModule.providers` への手動追加は不要。

#### 2‑2. コンポーネントへの注入

```ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth.service';

@Component({
  selector: 'app-dashboard',
  template: `

Welcome! Your token: {{ token }}

`
})
export class DashboardComponent implements OnInit {
  token = '';
  constructor(private auth: AuthService) {}
  ngOnInit() {
    this.token = this.auth.getToken();
  }
}
```

`constructor(private auth: AuthService)` が DI の核心。型情報から Angular が AuthService を解決しインスタンスを注入します。

#### 2‑3. ローカルスコープの DI

```ts
@Component({
  selector: 'app-settings',
  providers: [SettingsService]  // このコンポーネント配下だけで共有
})
export class SettingsComponent {}
```

同一サービスでもスコープを切ることで **状態の局所化** が可能です。

---

### 3. DI を意識すべき代表的な場面

| シーン           | なぜ DI が効くか                                                    |
| ------------- | ------------------------------------------------------------- |
| **テスト**       | モック／スタブの注入で外部依存を排除し、失敗パターンも容易に再現                              |
| **マルチ環境切り替え** | `useClass` / `useFactory` プロバイダで開発 ↔ 本番を一行でスワップ               |
| **外部サービス抽象化** | Stripe → PayPal のような決済ゲートウェイ交換も呼び出し側はノータッチ                    |
| **共有状態管理**    | AuthService, ThemeService などアプリ全体で 1 つなら `providedIn: 'root'` |
| **状態分離**      | 特定フローだけのローカルカウンタやウィザード進捗は Component.providers で隔離             |

---

### 4. Angular テストと DI

#### 4‑1. モックサービスでのユニットテスト

```ts
class MockAuthService {
  getToken() { return 'mock-token'; }
}

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [DashboardComponent],
    providers: [{ provide: AuthService, useClass: MockAuthService }]
  }).compileComponents();
});
```

* `provide` に本物の型、`useClass` にモックを指定することで **AuthService → MockAuthService** へ差し替え。
* HTTP 通信や複雑な副作用をテストから完全に排除し、**高速かつ安定**した検証が可能。

#### 4‑2. `useValue` と `spyOn`

```ts
providers: [{ provide: ConfigToken, useValue: { apiBase: 'http://localhost:3000' } }]
```

* `useValue` は単純な定数を注入したいときに便利。
* Jasmine の `spyOn(service, 'method')` と組み合わせれば呼び出し回数や引数検証も容易。

---

### 5. 実装切り替えのサンプル

環境変数で開発用／本番用ロガーを差し替える例：

```ts
export interface Logger { log(msg: string): void; }

@Injectable()
export class DevLogger implements Logger {
  log(msg: string) { console.debug('[DEV]', msg); }
}

@Injectable()
export class ProdLogger implements Logger {
  log(msg: string) { /* サーバー転送など */ }
}

@NgModule({
  providers: [
    {
      provide: Logger,
      useClass: environment.production ? ProdLogger : DevLogger
    }
  ]
})
export class CoreModule {}
```

ビルド時に自動で Prod/Dev が入れ替わるため、**コード改修ゼロで実装を切り替え**られます。

---

### 6. よくある誤解と真実

| 誤解                    | 真実                                                          |
| --------------------- | ----------------------------------------------------------- |
| DI はテストを難しくする         | むしろ **テストを容易**にするために存在する                                    |
| Singleton はアンチパターン    | Angular の DI が管理する Singleton は **グローバル状態を適切に隔離**し副作用を最小限に保つ |
| `@Injectable()` だけで十分 | `providedIn` を適切に設定しないと **インジェクタに登録されずエラー**                 |
| DI は大規模プロジェクト向け       | 小規模 SPA でも **テスト・拡張・保守**の観点で恩恵大                             |

---

### 7. DI スコープ戦略 quick‑reference

```
root injector    :  アプリ全体のシングルトン。AuthService, ThemeService
module injector  :  Lazy loaded module ごとに隔離。FeatureService
component injector:  特定 UI 階層内のみ。WizardStateService
```

「この依存性は誰と共有するのが最適か？」を常に問い、他コンポーネントに不要な状態は極力ローカルスコープへ閉じ込めるとバグ抑制に効きます。

---

### 8. まとめ

依存性の注入は**設計・テスト・運用**のあらゆる局面でコードをしなやかに保つ鍵です。Angular ではフレームワークが DI コンテナを提供しているため、`@Injectable({ providedIn: ... })` とコンストラクタインジェクションという **最小限の作法**を覚えるだけで、

1. **テスト容易性**：モック差替えで外部依存ゼロのユニットテスト
2. **実装柔軟性**：開発 ↔ 本番、A社 API ↔ B社 API の切替がワンライナー
3. **スコープ制御**：Singleton・モジュール・コンポーネント単位で状態分離

を自然に手に入れられます。もし「DI を使わずに直接 new しているクラス」が残っているなら、その都度「モック化できるか？ 別実装に耐えられるか？」と問い直してみてください。DI に置き換える価値は高いはずです。

> **“ソフトウェアの柔軟性は、依存を注入できるかどうかで決まる。”**

この一文を胸に、次のリファクタリング／新規実装で DI を味方につけていきましょう！

---

### 参考リンク

* Angular 公式 Docs: Dependency Injection
* Clean Architecture: Robert C. Martin
* Testing Angular Applications: Jesse Palmer & Corinna Cohn