依存性逆転の原則

SOLID の D - 上位モジュールは下位モジュールに依存せず、両者とも抽象に依存すべきという設計原則

SOLID設計原則

依存性逆転の原則とは

依存性逆転の原則 (Dependency Inversion Principle, DIP) は、SOLID 原則の D にあたる設計原則で、Robert C. Martin が 1996 年に提唱した。

  1. 上位モジュールは下位モジュールに依存してはならない。両者とも抽象に依存すべきである
  2. 抽象は詳細に依存してはならない。詳細が抽象に依存すべきである

Before / After

// ❌ DIP 違反: OrderService が DynamoDBRepo に直接依存
// DynamoDB を PostgreSQL に変更すると OrderService も変更が必要
class OrderService {
  private repo = new DynamoDBOrderRepo();  // 具体的な実装に依存
  async getOrder(id: string) { return this.repo.findById(id); }
}

// ✅ DIP 準拠: OrderService は抽象 (interface) に依存
// DynamoDB → PostgreSQL の変更は、アダプターの差し替えだけで済む
interface OrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
}

class OrderService {
  constructor(private readonly repo: OrderRepository) {}
  async getOrder(id: string) { return this.repo.findById(id); }
}

// 具体的な実装は interface に依存する
class DynamoDBOrderRepo implements OrderRepository { /* ... */ }
class PostgresOrderRepo implements OrderRepository { /* ... */ }

依存の方向

DIP 違反 (依存が下向き):
  OrderService → DynamoDBOrderRepo → DynamoDB

DIP 準拠 (依存が抽象に向く):
  OrderService → OrderRepository (interface) ← DynamoDBOrderRepoPostgresOrderRepo

「逆転」の意味: 通常は上位が下位に依存するが、DIP では下位 (DynamoDBOrderRepo) が上位の定義した抽象 (OrderRepository) に依存する。依存の方向が逆転している。

DIP と DI の関係

概念 種類 役割
DIP (依存性逆転の原則) 設計原則 「何に依存すべきか」を定義
DI (依存性注入) 実装パターン 「どう依存を渡すか」を実現
IoC (制御の反転) アーキテクチャ概念 「誰が依存を組み立てるか」を決定

DIP は「抽象に依存せよ」という原則、DI は「依存をコンストラクタで渡す」という実装手法だ。DIP を実現するために DI を使う。

Lambda での実践

// interface (上位モジュールが定義)
interface NotificationPort {
  send(userId: string, message: string): Promise<void>;
}

// 本番: SES アダプター
class SESAdapter implements NotificationPort {
  async send(userId: string, message: string) { await ses.sendEmail(/* ... */); }
}

// テスト: インメモリアダプター
class InMemoryAdapter implements NotificationPort {
  sent: { userId: string; message: string }[] = [];
  async send(userId: string, message: string) { this.sent.push({ userId, message }); }
}

DIP を適用すべきケース

  • 外部サービス (DB、メール、API) との境界
  • テスト時にモックに差し替えたい依存
  • 将来的に実装が変わる可能性がある箇所

すべてのクラスに interface を定義する必要はない。変更の可能性が低い内部ロジックには過剰だ。

全体像を把握するには関連書籍も有用。

関連用語