第03回: 依存性の注入(DI) — クラスを「差し替え可能」にする設計の核心

章: 第1章: オブジェクト指向の深化

「テストのためにDBが必要」になっていませんか?

クラス内部で new DBConnection() を書いたとき、そのクラスは特定のDB実装と密結合します。テストのたびに本物のDBが必要になり、実行が遅く、不安定になります。

依存性の注入(DI)は、必要なオブジェクトを外から渡すことでこの問題を解消します。「作る責務」と「使う責務」を分けるのが核心です。

DIの3つの形


<?php
// コンストラクタ注入(最も推奨)
class OrderService {
    public function __construct(
        private readonly OrderRepository $repo
    ) {}
    public function find(int $id): Order {
        return $this->repo->findById($id);
    }
}

// セッター注入(任意の依存を後から設定したいとき)
class NotificationService {
    private ?Logger $logger = null;
    public function setLogger(Logger $logger): void {
        $this->logger = $logger;
    }
}

// メソッド注入(メソッドごとに異なる依存が必要なとき)
class ReportGenerator {
    public function generate(Formatter $formatter): string {
        return $formatter->format($this->data);
    }
}
注入方法 使いどころ 注意点
コンストラクタ注入 ほぼすべてのケース 必須の依存に使う
セッター注入 任意の依存 オブジェクト生成後に依存が変わる場合
メソッド注入 リクエストごとに依存が変わる場合 使いすぎると設計が複雑になる

チェックポイント: コンストラクタ注入が基本です。セッター・メソッド注入は本当に必要な場面に限定しましょう。

DI前後の比較


<?php
// NG: クラス内部でインスタンスを生成 → テストで差し替え不可
class OrderService {
    private PdoOrderRepository $repo;
    public function __construct() {
        $this->repo = new PdoOrderRepository(new PDO('mysql:...'));
    }
}

// OK: 外から注入 → テスト時にモックを渡せる
class OrderService {
    public function __construct(private OrderRepository $repo) {}
}

// テストでの使用例
$mockRepo = $this->createMock(OrderRepository::class);
$service  = new OrderService($mockRepo);

チェックポイント: 「このクラスのテストを書こうとしたとき、DBや外部APIのセットアップが必要になる」と感じたら、DIを使うタイミングです。

なぜDIが重要なのか

  • テスタビリティ: 外部依存をモックに差し替えられるため、高速な単体テストが書ける
  • 差し替え容易性: 実装(MySQL→PostgreSQL、メール→Slack)を切り替えてもビジネスロジックを変更不要
  • 責務の明確化: 「依存を作る責務」と「依存を使う責務」が分離され、クラスがシンプルになる

まとめ & 次のステップ

  • DIはクラス間の結合を緩くするための手法で、依存を外から渡すことが本質
  • コンストラクタ注入が最も一般的で推奨される形式
  • DIにより、テストでのモック差し替えと本番での実装切り替えが容易になる

次回はDIコンテナを学びます。依存関係が増えてきたとき、手動のDI管理を自動化する仕組みです。

Related Articles