В чем разница между Service Locator и DI?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем разница между Service Locator и DI?
Service Locator и Dependency Injection (DI) — это два паттерна для управления зависимостями в приложении. Оба решают одну проблему: как получить нужный объект без жёсткой привязки к его конкретной реализации. Но они принципиально отличаются подходом.
Service Locator
Service Locator — это паттерн, где есть централизованный реестр объектов. Когда нужен объект, ты обращаешься к локатору и спрашиваешь его.
// Service Locator паттерн
class ServiceLocator {
private val services = mutableMapOf<Class<*>, Any>()
fun <T> register(clazz: Class<T>, instance: T) {
services[clazz] = instance as Any
}
fun <T> get(clazz: Class<T>): T {
return services[clazz] as T
}
}
val locator = ServiceLocator()
locator.register(UserRepository::class.java, UserRepositoryImpl())
locator.register(UserService::class.java, UserService(locator))
// Использование
class MyViewModel {
private val userService = locator.get(UserService::class.java)
}
Dependency Injection
Dependency Injection — это паттерн, где зависимости передаются в объект при его создании, обычно через конструктор.
// Dependency Injection паттерн
class UserService(private val userRepository: UserRepository) {
fun getUser(id: String) = userRepository.getUser(id)
}
class MyViewModel(private val userService: UserService) {
fun loadUser(id: String) = userService.getUser(id)
}
// Использование
val repository = UserRepositoryImpl()
val service = UserService(repository)
val viewModel = MyViewModel(service)
Ключевые различия
| Аспект | Service Locator | Dependency Injection |
|---|---|---|
| Получение зависимостей | Просишь у локатора | Передаёшь при создании |
| Контроль над зависимостями | Неявный (скрытый в коде) | Явный (видно в конструкторе) |
| Тестирование | Сложнее (нужно мокировать локатор) | Легче (просто передаёшь mock) |
| Видимость зависимостей | Скрыта внутри кода | Видна в сигнатуре |
| Связность | Высокая (зависимость от локатора) | Низкая (только от интерфейсов) |
| Производительность | Быстро при малом количестве типов | Минимальный overhead |
Тестирование
Service Locator — сложнее:
// Тестирование с Service Locator
@Test
fun testUserViewModel() {
val mockRepository = mockk<UserRepository>()
every { mockRepository.getUser(any()) } returns User(1, "Test")
// Нужно заменить в локаторе
locator.register(UserRepository::class.java, mockRepository)
val viewModel = MyViewModel() // внутри она достанет из локатора
// Проблема: скрыто в коде, где именно берётся mock?
}
DI — проще:
// Тестирование с DI
@Test
fun testUserViewModel() {
val mockRepository = mockk<UserRepository>()
every { mockRepository.getUser(any()) } returns User(1, "Test")
val service = UserService(mockRepository)
val viewModel = MyViewModel(service)
// Ясно, что тестируется с mock
assertEquals(expected, viewModel.loadUser("1"))
}
Android примеры
Service Locator (Koin когда-то использовал этот паттерн):
val myModule = module {
single { UserRepositoryImpl() as UserRepository }
factory { UserService(get()) }
}
startKoin { modules(myModule) }
class MyViewModel : ViewModel() {
private val userService: UserService = get()
}
DI (Hilt — рекомендуемый подход):
@HiltViewModel
class MyViewModel @Inject constructor(
private val userService: UserService
) : ViewModel() {}
@Provides
@Singleton
fun provideUserRepository(): UserRepository = UserRepositoryImpl()
@Provides
fun provideUserService(repository: UserRepository) = UserService(repository)
Проблемы Service Locator
- Скрытые зависимости — не видно из сигнатуры, какие зависимости нужны
- Сложнее тестировать — нужно мокировать локатор
- Runtime ошибки — если забыл зарегистрировать класс, ошибка только при вызове
- Global state — локатор это по сути глобальное состояние
- Порядок регистрации — может быть важен порядок регистрации
Преимущества DI
- Явные зависимости — видно в конструкторе
- Легче тестировать — просто передаёшь mock
- Compile-time ошибки — если забыл передать зависимость, IDE ошибку покажет
- Нет глобального state — каждый объект независим
- Гибкость — каждому объекту свои зависимости
Мой совет
На современном Android используй Dependency Injection через Hilt. Это Google-official решение, которое:
- Интегрировано с MVVM
- Поддерживает все komponenty Android
- Имеет compile-time safety
- Легче тестировать
- Это де-факто стандарт
Service Locator — это anti-pattern в современной разработке. Он был популярен когда DI фреймворков не было, но сейчас это не рекомендуется.