🐕

DI(依存性の注入)を丁寧に説明してみた

に公開

依存性の注入(Dependency Injection, DI)がわからない人へ

DIは、オブジェクトが必要とする依存関係を「外部から渡す」設計手法のことです。つまり、「どの具体的な実装を使うかを決めるのはオブジェクト自身ではなく、外部のコードが決める」ということです。

とまあこういうことなんですが、そんなこと言われてもよく分からんよ。という人たちのために、頑張って勉強したことをわかりやすくまとめたので、共有してあげようと思う。

今回は、TypeScriptのクラスを使って説明しています。DIそのものがオブジェクト指向をよくするためのSOLID原則の1つということもあるからです。特にDDDではドメイン駆動設計ということでドメインが他の要素に依存しないことを前提としてあげる必要がある。

※SOLID原則知らない人はこれ読んどいてください👇

【SOLID原則】依存性逆転の原則 - DIP

それでは本題に入りましょう。今回は下記のようなユースケースを想定したオブジェクトを作成していきます。

📱 ユースケース:ショッピングアプリの通知システム

  • 目的: 商品を購入したユーザーに通知を送る。
  • 課題: 通知方法(メール・SMS・アプリ内通知)を柔軟に変更できるようにしたい。

まずはDIなしバージョンで見てみましょう。ここでは、メール通知クラス(EmailNotifier)とオーダーサービスクラス(OrderService)を作成しています。

DIなし(通知方法を固定)

class EmailNotifier {
  sendNotification(message: string): void {
    console.log(`📧 Email: ${message}`);
  }
}

class OrderService {
  private notifier = new EmailNotifier(); // EmailNotifierに直接依存している

  placeOrder(user: string, item: string): void {
    console.log(`🛒 ${user} has purchased ${item}`);
    this.notifier.sendNotification(`Thank you for purchasing ${item}, ${user}!`);
  }
}

// 実行
const orderService = new OrderService();
orderService.placeOrder("Alice", "Laptop");

この時、オーダーサービスクラスは、private notifier = new EmailNotifier(); で直接クラスのインスタンスを作成している。

つまり、オーダーサービスクラスはメール通知クラスに依存していると言える。

ではこれがなぜダメなのかみていこう。

❌ 問題点

  • OrderServiceEmailNotifier に直接依存しているため、通知方法を変更するには OrderService のコードを修正しなければならない

  • あるいは、SmsNotifier というクラスを作成して、OrderService で呼び出さないといけない

  • 例えば、SMS通知を追加するには、OrderService の中身を変更しないといけない

    • private notifier = new EmailNotifier();private notifier = new SmsNotifier(); にしてあげないといけない。

つまり、通知方法の変更をすればいいだけなのに、オーダーサービスにも修正範囲が拡大してしまっている。


DIあり(通知方法を外部から渡す)

typescript
コピーする編集する
// 通知の共通インターフェース
interface Notifier {
  sendNotification(message: string): void;
}

// Email通知
class EmailNotifier implements Notifier {
  sendNotification(message: string): void {
    console.log(`📧 Email: ${message}`);
  }
}

// SMS通知
class SMSNotifier implements Notifier {
  sendNotification(message: string): void {
    console.log(`📲 SMS: ${message}`);
  }
}

// アプリ内通知
class AppNotifier implements Notifier {
  sendNotification(message: string): void {
    console.log(`📱 App Notification: ${message}`);
  }
}

// 注文サービス
class OrderService {
  private notifier: Notifier; // 依存関係を抽象(Notifier)に依存させる

  constructor(notifier: Notifier) {
    this.notifier = notifier;
  }

  placeOrder(user: string, item: string): void {
    console.log(`🛒 ${user} has purchased ${item}`);
    this.notifier.sendNotification(`Thank you for purchasing ${item}, ${user}!`);
  }
}

// 通知方法を変更しながら実行
const emailOrderService = new OrderService(new EmailNotifier());
emailOrderService.placeOrder("Alice", "Laptop");
// 📧 Email: Thank you for purchasing Laptop, Alice!

const smsOrderService = new OrderService(new SMSNotifier());
smsOrderService.placeOrder("Bob", "Smartphone");
// 📲 SMS: Thank you for purchasing Smartphone, Bob!

const appOrderService = new OrderService(new AppNotifier());
appOrderService.placeOrder("Charlie", "Headphones");
// 📱 App Notification: Thank you for purchasing Headphones, Charlie!


✅ DIを使うとどう便利になるのか?

  1. 通知方法を簡単に切り替えられる

    OrderService のコードを一切変更せずに、メール/SMS/アプリ通知を切り替えられる。

  2. 新しい通知方法を追加しても OrderService を変更しなくていい

    Notifier を実装する PushNotifierSlackNotifier を作るだけで追加できる。

  3. テストがしやすくなる

    typescript
    コピーする編集する
    class MockNotifier implements Notifier {
      sendNotification(message: string): void {
        console.log(`✅ [TEST] Notification sent: ${message}`);
      }
    }
    
    const testOrderService = new OrderService(new MockNotifier());
    testOrderService.placeOrder("TestUser", "TestItem");
    
    

    → 実際にメールやSMSを送らずに、通知が適切に処理されているかテストできる。


💡 まとめ

DIなし DIあり
依存の固定 OrderServiceEmailNotifier に直接依存 Notifier に依存し、具体的な通知方法は外部で決める
変更のしやすさ 通知方法を変えるたびに OrderService を修正 どの通知方法を使うかを簡単に切り替え
新機能追加 OrderService を直接書き換える必要あり 新しい Notifier を追加するだけでOK
テストのしやすさ 実際に console.log が呼ばれるため、テストしづらい MockNotifier で通知処理のテストが簡単

📌 結論

DIを使うことで、「通知の出し方」を自由に変更できる し、新しい通知方法を追加しても OrderService を修正する必要がない

柔軟で変更しやすい設計になるから、アプリが成長しても管理しやすい! 🚀

参考

はじめに|【DDD入門】TypeScript × ドメイン駆動設計ハンズオン

ドメイン駆動設計の定義についてEric Evansはなんと言っているのか[DDD] - little hands' lab

Discussion