← Назад к вопросам
Как реализуется принцип Dependency Inversion в Clean Architecture?
3.0 Senior🔥 151 комментариев
#Dependency Injection#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализуется Dependency Inversion в Clean Architecture?
Dependency Inversion (DI) — один из 5 принципов SOLID. Суть: высокоуровневые модули НЕ должны зависеть от низкоуровневых, оба должны зависеть от абстракций.
1. Проблема: Tight Coupling (неправильно)
// НЕПРАВИЛЬНО: прямая зависимость
class UserRepository {
fun getUser(id: Int): User {
val response = HttpClient.get("/users/$id")
return parseUser(response)
}
}
class UserViewModel(private val repo: UserRepository) {
fun loadUser(id: Int) {
val user = repo.getUser(id)
}
}
// Проблемы:
// - Сложно тестировать (нельзя подменить HttpClient)
// - Сложно менять реализацию (заменить API на БД)
// - Высокая связанность слоёв
2. Решение: Dependency Inversion через интерфейсы
// ПРАВИЛЬНО: через интерфейсы
// 1. Абстракция (контракт)
interface UserDataSource {
suspend fun getUser(id: Int): User
}
// 2. Реализация (конкретная)
class ApiUserDataSource : UserDataSource {
override suspend fun getUser(id: Int): User {
val response = httpClient.get("/users/$id")
return parseUser(response)
}
}
// 3. Repository зависит от ИНТЕРФЕЙСА
class UserRepository(
private val dataSource: UserDataSource
) {
suspend fun getUser(id: Int): User = dataSource.getUser(id)
}
// 4. ViewModel зависит от ИНТЕРФЕЙСА
class UserViewModel(
private val repo: UserRepository
) {
fun loadUser(id: Int) {
val user = repo.getUser(id)
}
}
3. Архитектурные слои Clean Architecture
Presentation (UI)
↓ зависит от Application
Application (Use Cases)
↓ зависит от Domain
Domain (Entities, Interfaces)
↑ реализует Infrastructure
Infrastructure (Repository, API, Database)
4. Полный пример Clean Architecture
// DOMAIN (правила бизнеса)
data class User(
val id: Int,
val name: String,
val email: String
)
interface UserRepository {
suspend fun getUser(id: Int): User
suspend fun saveUser(user: User)
}
// APPLICATION (use cases)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(userId: Int): User {
return repository.getUser(userId)
}
}
// INFRASTRUCTURE (реальная реализация)
class ApiUserRepository(
private val httpClient: HttpClient
) : UserRepository {
override suspend fun getUser(id: Int): User {
val response = httpClient.get("/users/$id")
return User(
id = response["id"].asInt,
name = response["name"].asString,
email = response["email"].asString
)
}
override suspend fun saveUser(user: User) {
httpClient.post("/users", user)
}
}
// PRESENTATION (UI)
class UserViewModel(
private val getUser: GetUserUseCase
) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
fun loadUser(id: Int) {
viewModelScope.launch {
_user.value = getUser(id)
}
}
}
5. Dependency Injection: как передать зависимости?
Способ 1: Hilt (рекомендуется)
// 1. Определить в Module
module
object RepositoryModule {
fun provideUserRepository(
httpClient: HttpClient
): UserRepository {
return ApiUserRepository(httpClient)
}
}
// 2. Использовать в ViewModel
class UserViewModel constructor(
private val getUser: GetUserUseCase
) : ViewModel() { ... }
// 3. Использовать в Activity
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
}
Способ 2: Manual (простой случай)
val httpClient = HttpClient()
val repository = ApiUserRepository(httpClient)
val getUser = GetUserUseCase(repository)
val viewModel = UserViewModel(getUser)
6. Инверсия: слои зависят от абстракций
// НЕПРАВИЛЬНО: слой зависит от конкретной реализации
class UserViewModel {
private val repository = ApiUserRepository()
}
// ПРАВИЛЬНО: слой зависит от интерфейса
class UserViewModel(
private val repository: UserRepository
) {
// Зависит от интерфейса, не от конкретной реализации
}
7. Тестирование с DI (главное преимущество)
// Определить mock-реализацию
class MockUserRepository : UserRepository {
override suspend fun getUser(id: Int): User {
return User(id, "Test User", "test@example.com")
}
override suspend fun saveUser(user: User) {}
}
// Тестировать с mock'ом
class UserViewModelTest {
fun testLoadUser() {
val mockRepository = MockUserRepository()
val getUser = GetUserUseCase(mockRepository)
val viewModel = UserViewModel(getUser)
viewModel.loadUser(1)
assertEquals("Test User", viewModel.user.value?.name)
}
}
8. Правило зависимостей
- Domain НЕ зависит ни от чего
- Application зависит только от Domain
- Infrastructure реализует интерфейсы из Domain
- Presentation зависит от Application
- ВСЕ зависят от интерфейсов, а не от конкретных реализаций
9. Результаты DI + Clean Architecture
Тестируемость: подменяешь интерфейсы на mock'и Гибкость: меняешь реализацию без изменения слоёв выше Масштабируемость: легко добавлять новые фичи Чистота кода: слои не перемешаны
Итог
- DI = Dependency Injection через конструктор
- Зависимости от интерфейсов, а не конкретных классов
- Инверсия: слои зависят от Domain, а не наоборот
- Hilt для автоматизации
- Главное преимущество: простое тестирование и гибкая архитектура