12k
All articles

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

依存性逆転の原則では、抽象化がTypeScript、Python、Javaコードにおける上位モジュールと下位モジュールを分離する仕組みを解説する。

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

依存性逆転の原則(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を達成するための技術です。

なぜDIPはテストを容易にするのですか?

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

JavaScriptでDIPを適用するにはインターフェースが必要ですか?

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

Listen to your bugs 🧘, with OpenReplay

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

We use cookies to improve your experience. By using our site, you accept cookies.