はじめに
SOLID 原則の最後を飾るのが DIP(依存関係逆転の原則)。
これは「高水準のモジュールを低水準に縛らない」ための考え方で、拡張性・テスト容易性・保守性 を大幅に向上させます。
1. 定義
依存関係逆転の原則(DIP) とは:
高水準モジュールは低水準モジュールに依存すべきではない。両者は抽象に依存すべきである。
抽象は詳細に依存すべきではない。詳細が抽象に依存すべきである。
2. 直感的な例
DIP 違反(具象クラスに直接依存)
class ApiUserRepository {
fun fetchUser(): String = "user from API"
}
class UserService {
private val repository = ApiUserRepository()
fun getUser() = repository.fetchUser()
}
-
UserServiceがApiUserRepositoryに直依存 - 実装を差し替える(DB保存、キャッシュ利用など)ときに サービス側まで修正が必要
DIP 準拠(抽象に依存)
interface UserRepository {
fun fetchUser(): String
}
class ApiUserRepository : UserRepository {
override fun fetchUser() = "user from API"
}
class DbUserRepository : UserRepository {
override fun fetchUser() = "user from DB"
}
class UserService(private val repository: UserRepository) {
fun getUser() = repository.fetchUser()
}
-
UserServiceは 抽象UserRepositoryに依存 - 実装(API/DB/Mock)は 注入(DI) で切り替え可能
3. Flutter / Android での例
Flutter(DI with Riverpod)
abstract class WeatherRepository {
Future<String> fetch();
}
class ApiWeatherRepository implements WeatherRepository {
@override
Future<String> fetch() async => "Sunny from API";
}
final weatherRepoProvider = Provider<WeatherRepository>((ref) {
return ApiWeatherRepository();
});
class WeatherService {
final WeatherRepository repo;
WeatherService(this.repo);
Future<String> getWeather() => repo.fetch();
}
→ Provider の切り替えでテスト用リポジトリに差し替え可能。
Android(Kotlin + Hilt)
interface Logger {
fun log(message: String)
}
class ConsoleLogger @Inject constructor() : Logger {
override fun log(message: String) = println("Console: $message")
}
class FileLogger @Inject constructor() : Logger {
override fun log(message: String) { /* ファイル出力 */ }
}
class UserService @Inject constructor(
private val logger: Logger
) {
fun createUser(name: String) {
logger.log("User created: $name")
}
}
→ Logger の実装は DI コンテナ(Hilt/Koin)で差し替え可能。
4. DIP のメリット
- 実装の差し替えが容易(API → DB → キャッシュ)
- テスト容易性が向上(モックリポジトリを注入可能)
- 循環依存を防ぐ(高水準が低水準に縛られない)
- Clean Architecture との親和性が高い
5. Clean Architecture との関係
DIP は Clean Architecture の依存ルール そのものです。
- 内側(Domain, Application)は 抽象 を持つ
- 外側(Infrastructure)が 詳細実装 を依存する
- これにより「内側は外側を知らない」=独立性とテスト性が担保される
6. 実務での適用ポイント
- インターフェース駆動設計(ポート/アダプター) を徹底
- テスト時はモック実装 を注入する仕組みを用意
-
依存方向をレビューでチェック
→ 「上位レイヤーが下位の具象に依存していないか?」 -
過剰抽象は避ける
→ 変更可能性が高い部分だけインターフェース化する
まとめ
- DIP = 抽象に依存し、詳細は外側に閉じ込める
- これにより 拡張性・テスト容易性・保守性 が飛躍的に向上する
- Clean Architecture の「依存ルール」の基盤