Back

依存性逆転の原則とは?簡単に説明

依存性逆転の原則とは?簡単に説明

依存性逆転の原則(Dependency Inversion Principle、DIP)は、オブジェクト指向設計の5つのSOLID原則の1つです。依存関係の方向を—具体的な実装から抽象的な契約へと—変えることで、柔軟で疎結合なシステムを作成するのに役立ちます。この記事では、DIPを平易な言葉で理解し、すぐに適用できる例を紹介します。

重要なポイント

  • 上位モジュールは下位モジュールに依存すべきではなく、両方とも抽象に依存すべき
  • DIPは柔軟でテスト可能なアーキテクチャを可能にする
  • 複数の言語での実世界の例を通じてDIPの適用方法を学ぶ

公式の定義

上位モジュールは下位モジュールに依存すべきではない。両方とも抽象に依存すべきである。抽象は詳細に依存すべきではない。詳細が抽象に依存すべきである。

これを分解しましょう:

  • 上位モジュール:ビジネスロジックを含む(例:注文の処理)
  • 下位モジュール:特定のタスクを処理する(例:メール送信)
  • 抽象:実装ではなく、振る舞いを定義するインターフェースまたは基底クラス

視覚的な類推

注文が行われたときにメール通知を送信するOrderServiceがあるとします。

DIPなし:

OrderService --> EmailService

OrderServiceEmailServiceに密接に結合しています。簡単に交換したりモックしたりできません。

DIPあり:

OrderService --> INotificationService <-- EmailService

今や両方のモジュールが抽象(INotificationService)に依存しています。

コード例:DIPなし(TypeScript)

class EmailService {
  send(message: string) {
    console.log(`Sending email: ${message}`);
  }
}

class OrderService {
  constructor(private emailService: EmailService) {}

  placeOrder() {
    this.emailService.send("Order placed");
  }
}

これはOrderServiceEmailServiceに密接に結合しています。

リファクタリング:DIPあり(TypeScript)

interface INotificationService {
  send(message: string): void;
}

class EmailService implements INotificationService {
  send(message: string) {
    console.log(`Sending email: ${message}`);
  }
}

class OrderService {
  constructor(private notifier: INotificationService) {}

  placeOrder() {
    this.notifier.send("Order placed");
  }
}

PythonでのDIP

from abc import ABC, abstractmethod

class NotificationService(ABC):
    @abstractmethod
    def send(self, message: str):
        pass

class EmailService(NotificationService):
    def send(self, message: str):
        print(f"Sending email: {message}")

class OrderService:
    def __init__(self, notifier: NotificationService):
        self.notifier = notifier

    def place_order(self):
        self.notifier.send("Order placed")

# 使用例
service = OrderService(EmailService())
service.place_order()

JavaでのDIP

interface NotificationService {
    void send(String message);
}

class EmailService implements NotificationService {
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

class OrderService {
    private NotificationService notifier;

    public OrderService(NotificationService notifier) {
        this.notifier = notifier;
    }

    public void placeOrder() {
        notifier.send("Order placed");
    }
}

// 使用例
OrderService service = new OrderService(new EmailService());
service.placeOrder();

DIPが重要な理由

  • テスト容易性:実際の依存関係をモックやフェイクに置き換えられる
  • 柔軟性:上位ロジックに触れることなく実装を切り替えられる
  • 関心の分離:各モジュールは1つの仕事を行い、契約を通じて通信する

よくある誤解:DIP ≠ 依存性注入

関連していますが、同じではありません:

  • DIPは誰が誰に依存するか(依存関係の方向)に関するもの
  • 依存性注入はDIPを適用する一つの方法—依存関係をハードコーディングする代わりに注入する

DIPを使用するタイミング

以下の場合に使用します:

  • 基盤となる実装を気にしないビジネスロジックを書きたい場合
  • 階層化またはモジュール化されたアプリケーションに取り組んでいる場合
  • テスト容易性や拡張性を構築している場合

結論

依存性逆転の原則は、依存関係の通常の方向を反転させることに関するものです—実装ではなく、抽象がアーキテクチャを定義するようにします。これにより、コードはより再利用可能で、テスト可能で、変更に対して堅牢になります。

よくある質問

上位モジュールと下位モジュールがお互いではなく抽象に依存するという設計原則です。

いいえ。DIPは原則です。依存性注入はDIPを達成するための技術です。

実際の依存関係を同じインターフェースに従うモックやスタブに置き換えることができるからです。

TypeScriptではインターフェースが役立ちますが、JavaScriptではオブジェクト形状の契約とパターンを使用して同じことを達成できます。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers