DIP:開発における依存関係逆転の基礎

著者: IT Sectr 公開日: 2026-05-12 読了時間: 9 分

DIP(Dependency Inversion Principle) — モジュール間の依存関係を構築するルールを定義するSOLIDの第5原則です:上位レベルのモジュールは下位レベルのモジュールに依存すべきではなく、両者は抽象化に依存すべきです。抽象化は詳細に依存すべきではなく、詳細は抽象化に依存すべきです。ロバート・マーティンによってClean Architecture (2017)で説明されたこの原則は、疎結合アーキテクチャの基盤にあります。この本によると、依存関係逆転の原則はアプリケーション層間の堅い結合を排除します。

主要ポイント

  • DIP — 依存関係逆転の原則、SOLIDの5番目、アーキテクチャの境界に関するもの
  • 上位レベルのモジュールは下位レベルのモジュールをインポートすべきではない — 抽象化のみ
  • DIP ≠ DI:依存関係逆転はアーキテクチャ原則、依存性注入はその実装方法
  • 抽象化は上位レベルのモジュールに属し、実装は下位レベルに属する
  • DIPは多層アーキテクチャにおける従来の依存関係階層を逆転させる

DIP(依存関係逆転の原則)とは?

DIP(Dependency Inversion Principle)は、モジュール間の依存関係の方向に対する従来の見方を逆転させる依存関係逆転の原則です。上位レベルのモジュール(ビジネスロジック)は下位レベルのモジュール(データベース、ネットワーク、UI)に直接依存すべきではありません。代わりに、両方のレベルが上位レベルのモジュールで定義された抽象化に依存します。

DIPの正式な定式化には2つのルールが含まれます:A — 上位レベルのモジュールは下位レベルのモジュールに依存すべきではなく、両者は抽象化に依存すべきです。B — 抽象化は詳細に依存すべきではなく、詳細は抽象化に依存すべきです。2番目のルールは最初のルールから導かれます:抽象化が詳細に依存する場合、上位レベルのモジュールの安定した基盤にはなり得ません。

DIPがない場合、典型的なアーキテクチャは次のようになります:BusinessLogic → DatabaseRepository — ビジネスロジックが具体的なリポジトリに直接依存します。DIPがある場合:BusinessLogic → DatabaseServiceInterface ← DatabaseRepository。BusinessLogicはDatabaseRepositoryの存在を知りません、ビジネスロジックの外部で実装されるDatabaseServiceインターフェースのみを知っています。

DIPにおける依存関係の方向

逆転とは、制御の流れと依存の流れが反対方向に向かうことを意味します。制御の流れは上から下へ:UI → ViewModel → UseCase → Repository。依存の流れは下から上へ:RepositoryはUseCaseで定義されたインターフェースを実装します。Repository(下位レベル)はUseCase(上位レベル)に依存します

この逆転が、DIPと通常の層分離との主な違いです。従来の階層型アーキテクチャでは、各層はその下の層に依存します。DIPベースのアーキテクチャでは、すべての層が抽象化に依存し、これらの抽象化の実装はインフラストラクチャ層に存在し、DIメカニズムを介して上位層に“プラグイン”されます。

依存関係逆転の原則の仕組み

DIPのメカニズムは、上位レベルのモジュールで抽象化を定義し、下位レベルのモジュールでそれらを実装することによって実現されます。上位レベルのモジュールは、必要な機能のためのインターフェースを宣言します。下位レベルのモジュールはこのインターフェースを実装します。配線(wiring)はアプリケーションのコンポジションルートで行われます。

既存のコードにDIPを導入するプロセス:下位レベルのモジュールのインターフェースを抽出し、このインターフェースを上位レベルのモジュール(または別の抽象化層)に移動し、上位モジュールの依存関係をインターフェースを使用するように書き換え、下位レベルのモジュールにこのインターフェースを実装させます。これらのステップの後、依存関係の方向が逆転しました

DIPにはコンポジションルートメカニズムが必要です — すべての依存関係が作成され相互に結び付けられるアプリケーション内のポイントです。AndroidではApplication.get()またはHiltコンポーネント、iOSではAppDelegateまたはSceneDelegateです。コンポジションルートは、コードが具体的な実装について知っている唯一の場所です。

DIPによる層の分離

DIPはアプリケーション層間のアーキテクチャ上の境界を作成します。ViewModelがUserRepositoryインターフェースに依存するとき、プレゼンテーション層とドメイン層の間に境界が形成されます:ViewModel(プレゼンテーション)はデータがどこから来るのか知りません。この境界により、ViewModelに影響を与えずにUserRepositoryの実装(Room → REST → Mock)を変更できます。このような境界が多いほど、アプリケーションはフレームワークやライブラリの変更に対してより回復力があります。

Googleが推奨するAndroidアーキテクチャでは、DIPはドメイン層に存在しRepositoryインターフェースに依存するUseCaseを介して実装されます。RepositoryImplはデータ層にあり、これらのインターフェースを実装します。プレゼンテーション層(ViewModel)はUseCaseに依存します。依存関係の方向はプレゼンテーションからドメインへ、ドメインからデータへ向かいます — しかし、どの層も他の層の具体的な実装について知りません。

DIPとDI(依存性注入)の違い

DIPとDIはしばしば混同されますが、異なる概念です。DIPはアーキテクチャ原則(何をするか:抽象化に依存する)。DIは実装パターン(どうやってするか:コンストラクタを介して依存関係を渡す)。DIPは“モジュールは何に基づくべきか?”という質問に答え、DIは“オブジェクトはどのように依存関係を取得するか?”に答えます。

依存性注入(Dependency Injection)は、コンストラクタ、メソッド、またはプロパティを介してオブジェクトに依存関係を注入する方法です。Kotlinクラスがコンストラクタを介してRepositoryインターフェースを受け取るとき — それがDIです。ViewModelクラスが具体的なRoomRepository実装ではなくRepositoryインターフェースに依存するという事実 — それがDIPです。DIはツールであり、DIPは目標です

DIフレームワークなしでDIPに従うことができます:コンポジションルートでの手動の依存関係配線もDI(手動DI)です。DIPに違反しながらDIフレームワーク(Dagger、Hilt、Koin)を使用することもできます:ViewModelがnew()を介して直接Repositoryオブジェクトを作成する場合 — フレームワークがインストールされていてもDIPに違反しています。DIPはアーキテクチャ上の決定であり、DIは技術的な詳細です

モバイル開発におけるDIPの例

データ層にDIPを適用するAndroidの例を見てみましょう。DIPがない場合、ViewModelは直接RoomDatabaseとDAOを作成します。DIPがある場合 — ViewModelはUserRepositoryインターフェースに依存し、具体的なRoomUserRepository実装は外部から提供されます。

kotlin
// 抽象化はドメイン層(上位レベル)に属する
interface UserRepository {
    fun getUser(id: Int): User
}

// ドメイン層は抽象化のみに依存する
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// データ層の実装はドメイン層の抽象化に依存する
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

// コンポジションルート
class AppModule {
    fun provideUserRepository(dao: UserDao): UserRepository {
        return RoomUserRepository(dao)
    }
}

Application Coordinatorとナビゲーションプロトコルを使用したiOSの例

swift
// ドメイン層のナビゲーション抽象化
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModelは抽象化に依存し、UIKitには依存しない
final class AuthViewModel {
    private let navigation: AuthNavigation

    init(navigation: AuthNavigation) {
        self.navigation = navigation
    }

    func onLoginSuccess() {
        navigation.navigateToHome()
    }
}

// Coordinator(UIKit層)はドメイン層のプロトコルを実装する
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // UIKitナビゲーションコード
    }
    func navigateToLogin() {
        // UIKitナビゲーションコード
    }
}

重要なポイント:AuthViewModel(ドメイン)はAppCoordinator(UIKit)の存在を知りません。AuthNavigationプロトコルのみを知っています。もし明日UIKitがSwiftUIに置き換えられても — AuthViewModelは変更を必要としません。DIPはドメイン層をUIフレームワークやライブラリから独立させます

DIPのためのツール:Dagger、Hilt、Koin

HiltはAndroidの標準DIツールであり、Googleが推奨しています。Jetpackに組み込まれており、ViewModel、Fragment、Service、その他のAndroidコンポーネントをサポートしています。Hiltは@Module、@Provides、@Injectアノテーションを介してコンポジションルートの作成を自動化します。Hiltを使用してもDIPの準拠は保証されません — UserRepositoryインターフェースはデータ層ではなくドメイン層で定義される必要があります。

Koinはコード生成やアノテーション処理なしの軽量なKotlin用DIフレームワークです。KoinのDSL(module、single、factory)は学習が容易ですが、依存関係のチェックはコンパイル時ではなく実行時に行われます。KoinはiOSサポートによりマルチプラットフォームプロジェクト(KMP)で人気があります。

Dagger 2はHiltの前身であり、今でも大規模プロジェクトで使用されています。Daggerはコンパイル時にDIコードを生成し、最大のパフォーマンスとビルド時のエラー診断を提供します。HiltはDaggerの上に構築され、簡略化されたAPIを提供します。新しいプロジェクトには、GoogleはHiltを主要なDIフレームワークとして推奨しています。

層ごとのDIモジュールの編成

DIモジュールはアーキテクチャ層に対応し、DomainModule、DataModule、PresentationModuleに分割されるべきです。DomainModuleは抽象化とUseCaseのみを提供します。DataModuleは抽象化のための実装を提供します。PresentationModuleはViewModelとUseCaseを結び付けます。この編成により、ドメイン層がインフラストラクチャライブラリから独立したままであることが保証されます。

DIフレームワーク間の移行時(例えばKoinからHiltへ)、DomainModuleの構造は変わりません — DataModuleとPresentationModuleの配線方法のみが変わります。DIPはドメインロジックの分離を保証し、DIフレームワークは技術的な配線メカニズムです。

よくある質問

常にDIPを適用すべきですか?

DIPはアーキテクチャ上の境界で必要です — アプリケーション層間(ドメイン → データ、プレゼンテーション → ドメイン)。同じ層内では、DIPは過剰になる可能性があります。例えば、ドメイン層内のユーティリティクラスStringFormatterは、置き換える理由がない限りインターフェースを必要としません。

DIPは依存性注入と同じですか?

いいえ。DIPは原則です:モジュールは抽象化に依存すべきです。DIはパターンです:オブジェクトは依存関係を自身で作成するのではなく外部から受け取ります。DIはDIPを実装する方法ですが、DIPはDIなしでも(ファクトリやサービスロケータを介して)遵守できます。DIなしのDIPは可能ですが、アーキテクチャ上の価値はありません。

DIPのためのインターフェースはどこで定義すべきですか?

インターフェースはそれを使用するモジュールに属します、実装するモジュールではありません。UserRepositoryはドメイン層で宣言され、データ層で実装されます。これがDIPの重要なルールです:抽象化の所有者は消費者であり、実装の提供者ではありません。

DIPはテストにどのように影響しますか?

DIPは分離された層でのテストを可能にします。UserRepository(インターフェース)に依存するViewModelは、データベースなしでモック実装を使用してテストできます。DIPがない場合、ViewModelはRoomUserRepositoryに依存し、各テストにデータベースのセットアップが必要になります。DIP + DIはテスト中に完全なモジュール分離を提供します。

AndroidにはどのDIフレームワークを選ぶべきですか?

HiltはAndroidプロジェクトの標準的な選択肢であり、Googleが推奨しています。KoinはKotlin Multiplatformプロジェクトの代替手段です。Dagger 2はHiltへの移行が正当化されない既存プロジェクト向けです。フレームワークの選択は、アーキテクチャレベルでDIPに従う必要性を排除しません。

まとめ

  • DIP(依存関係逆転の原則) — 抽象化を通じたアーキテクチャ上の境界に関するSOLIDの第5原則
  • 上位レベルのモジュールは下位レベルのモジュールに依存しない — 両者は抽象化に依存する
  • DIP ≠ DI:原則 vs 実装パターン;DIはツール、DIPは目標
  • 抽象化は消費者に属する(ドメイン層)、提供者ではない(データ層)
  • コンポジションルート — アプリケーション内で具体的な依存関係が組み立てられる唯一の場所
  • Hilt、Koin、Dagger — 配線を自動化するDIツールだが、DIPのアーキテクチャ上の決定を代替しない
  • ドメイン層はDIPに従って構築され、フレームワーク、UI、インフラストラクチャライブラリから独立したまま

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください