Introdução
O desenvolvimento do Android para DI tradicionalmente usa o Dagger 2 , uma estrutura de geração de código muito poderosa. Mas há um problema: é difícil para iniciantes usá-lo. Os princípios de DI são simples e diretos, mas Dagger os complica. Você pode reclamar do declínio absoluto da alfabetização dos programadores, mas o problema não desaparecerá.
Com o advento do Kotlin, tornou-se possível escrever coisas convenientes que seriam quase impossíveis usando Java. Uma dessas coisas foi Koin , que não é um DI, mas um Localizador de Serviços, que é tratado por muitos como antipadrão , e é por isso que muitas pessoas não o usam em princípio. Mas em vão, porque possui uma API muito concisa que simplifica a gravação e a manutenção do código.
Neste artigo, quero ajudar os iniciantes a descobrir a distinção entre Injeção de Dependência e Localizador de Serviço, mas não o próprio Koin.
Injeção de dependência
Primeiro de tudo, o que é injeção de dependência? Em palavras simples, é quando um objeto aceita dependências de fora, em vez de criar ou extraí-las. Vou dar um exemplo. Suponha que tenhamos uma interface Engine
, sua implementação e também uma classe Car
que depende Engine
. Sem DI, pode ser assim
interface Engine
class DefaultEngine: Engine
class Car {
private val engine: Engine = DefaultEngine()
}
fun main() {
val car = Car()
}
Se reescrevermos a classe Car
usando a abordagem DI, isso poderá acontecer:
class Car(private val engine: Engine)
fun main() {
val car = Car(DefaultEngine())
}
É simples - a classe Car
não sabe de onde vem a implementação Engine
, enquanto a substituição dessa mesma implementação é fácil, basta passá-la ao construtor.
Localizador de serviço
Service Locator. – , . Koin ServiceLocator, , API.
object ServiceLocator {
fun <reified T> register(factory: () -> T) { ... }
fun <reified T> resolve(): T { ... }
}
, . :
interface Engine
class DefaultEngine: Engine
class Car {
private val engine: Engine = ServiceLocator.resolve()
}
fun main() {
ServiceLocator.register<Engine> { DefaultEngine() }
val car = Car()
}
DI, Car
, , – Car
, . :
interface ServiceLocator {
fun <reified T> register(factory: () -> T)
fun <reified T> resolve(): T
}
class DefaultServiceLocator: ServiceLocator { ... }
class Car(private val serviceLocator: ServiceLocator) {
private val engine = serviceLocator.resolve<Engine>()
}
fun main() {
val serviceLocator = DefaultServiceLocator()
serviceLocator.register<Engine> { DefaultEngine() }
val car = Car(serviceLocator)
}
, Car
, . .. . :
class Car(private val engine: Engine)
fun main() {
ServiceLocator.register<Engine> { DefaultEngine() }
val car = Car(ServiceLocator.resolve<Engine>())
}
Isso é pura injeção de dependência. Com Koin, ficaria assim:
interface Engine
class DefaultEngine: Engine
class Car(private val engine: Engine)
fun carModule() = module {
factory<Engine> { DefaultEngine() }
factory { Car(get<Engine>()) }
}
fun main() {
val koin = startKoin {
modules(carModule())
}.koin
val car = koin.get<Car>()
}
Infelizmente, ainda precisamos entrar em contato com Koin para dependências, mas isso não contradiz os princípios da Injeção de Dependências.
ATUALIZAR Por solicitaçãokranid Vou dar o exemplo mais simples do Dagger 2.
interface Engine
class DefaultEngine: Engine
class Car @Inject constructor(private val engine: Engine)
@Module
class AppModule {
@Provides
fun provideEngine(): Engine = DefaultEngine()
}
@Component(modules = [AppModule.class])
interface AppComponent {
fun car(): Car
}
fun main() {
val daggerComponent = DaggerAppComponent.create()
val car = daggerComponent.car()
}