はじめに
ソフトウェア開発において、クラスが他のクラスを直接生成すると 依存関係が強く結びついてしまい、テストや拡張が難しくなります。
この問題を解決する設計手法が 依存性注入(Dependency Injection, DI) です。
DI は IoC(Inversion of Control:制御の反転) の一種であり、近年のフレームワークやライブラリ(Spring, Dagger, Hilt, Koin など)で広く採用されています。
依存性注入の基本アイデア
通常のコードでは、クラスが自分で依存を生成します。
// ❌ DIなし(強い結合)
class UserService {
private val repository = UserRepository()
}
これでは UserService が UserRepository の具体クラスに依存してしまい、差し替えやテストが困難です。
DIを使った例(コンストラクタインジェクション)
// インターフェースを定義
interface UserRepository {
fun findById(id: String): User
}
// DB実装
class DbUserRepository : UserRepository {
override fun findById(id: String): User { ... }
}
// テスト用実装
class MockUserRepository : UserRepository {
override fun findById(id: String): User = User("mock")
}
// サービスは抽象に依存する
class UserService(private val repository: UserRepository) {
fun getUser(id: String): User = repository.findById(id)
}
// 利用側で依存を注入
val repository = DbUserRepository()
val service = UserService(repository)
👉 UserService は「UserRepository を使う」という抽象に依存しているため、
実際に渡す実装が DB 版でもモック版でも問題なく動作します。
DIのメリット
-
疎結合化
クラス間の結びつきを弱め、柔軟な設計が可能 -
テスト容易性
モックやスタブを注入でき、ユニットテストがしやすい -
拡張性
実装を切り替えても利用側に影響が少ない -
保守性
変更が局所化され、大規模開発でも安心
DIの方法
- コンストラクタインジェクション(推奨・最も一般的)
- セッターインジェクション(後から設定可能な依存向き)
- インターフェース注入(専用メソッド経由、あまり使われない)
DIとデザインパターンの関係
- DI 自体は デザインパターンではなく設計手法
- ただし内部では Factory パターン や Service Locator パターン を利用
- 実際の DI 実装は DIコンテナ(Spring, Dagger, Hilt, Koin など) が担当
まとめ
- 依存性注入(DI) は、依存オブジェクトを外部から渡す設計手法
- IoC の一種であり、疎結合・テスト容易性・拡張性を高める
- 小規模なら直接クラス依存でもよいが、大規模・テスト重視ならインターフェースを使い DI を導入するのがベスト