¿Es Koin Dependency Injection o Service Locator?

Introducción


El desarrollo de Android para DI utiliza tradicionalmente Dagger 2 , un marco de generación de código muy potente. Pero hay un problema: es difícil para los principiantes usarlo. Los principios DI son simples y directos, pero Dagger los complica. Puede quejarse de la disminución absoluta en la alfabetización de los programadores, pero el problema no desaparecerá de esto.


Con el advenimiento de Kotlin, se hizo posible escribir cosas convenientes que serían casi imposibles usando Java. Una de estas cosas fue Koin , que no es un DI, sino un Localizador de servicios, que muchos consideran antipatrón , por lo que muchos no lo usan en principio. Pero en vano, porque tiene una API muy concisa que simplifica la escritura y el mantenimiento del código.


En este artículo, quiero ayudar a los principiantes a descubrir la distinción entre Inyección de dependencias y Localizador de servicios, pero no el propio Koin.


Inyección de dependencia


En primer lugar, ¿qué es la inyección de dependencia? En palabras simples, esto es cuando un objeto acepta dependencias del exterior, en lugar de crearlas o extraerlas. Daré un ejemplo. Supongamos que tenemos una interfaz Engine, su implementación y también una clase de la Carque depende Engine. Sin DI, podría verse así


interface Engine
class DefaultEngine: Engine

class Car {
  private val engine: Engine = DefaultEngine()
}

fun main() {
  val car = Car()
}

Si reescribimos la clase Carusando el enfoque DI, entonces esto puede suceder:


class Car(private val engine: Engine)

fun main() {
  val car = Car(DefaultEngine())
}

Es simple: la clase Carno sabe de dónde viene la implementación Engine, mientras que reemplazar esta implementación es fácil, simplemente páselo al constructor.


Servicio de localización


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>())
}

Esto es pura inyección de dependencia. Con Koin, se vería así:


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>()
}

Por desgracia, aún necesitamos contactar a Koin para las dependencias, pero esto de ninguna manera contradice los principios de la inyección de dependencia.


ACTUALIZAR Por solicitudkranid Daré el ejemplo más simple de 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() {
  // DaggerAppComponent –  ,   Dagger
  val daggerComponent = DaggerAppComponent.create()
  val car = daggerComponent.car()
}

All Articles