Kotlin 38. Inyección de dependencia y el uso de Hilt en Kotlin, Serie 1: Introducción a la inyección de dependencia

Aprendamos Kotlin juntos: Concepto: 25. Dependency InjectionInyección de Dependencia y el uso de Hilt en Kotlin, Serie 1: Introducción a la Inyección de Dependencia

En esta serie de blogs, presentaremos principalmente:

  • Dependency Injection(Inyección de dependencia) Introducción al concepto. He leído muchas introducciones sobre DI en Internet, pero estoy confuso. Aquí, lo presentamos de una manera fácil de entender.
  • Una introducción a la inyección manual de dependencias. Para que sea más fácil para todos entender a Hilt, primero presentamos cómo lograr efectos de inyección de dependencia manualmente.
  • Hilt anotaciones (anotaciones) introducción y casos de uso
  • Cómo usar Hilt en el caso de MVVM

Este blog presenta principalmente Dependency Injectionel concepto (Inyección de dependencia).



1 ¿Qué es DI?

Entonces, antes de comenzar a entender DI, primero comprendamos qué significa Dependencia en la programación.

Cuando la clase A usa alguna funcionalidad de la clase B, significa que la clase A tiene una dependencia de la clase B. En Java, antes de que podamos usar métodos de otra clase, primero necesitamos crear un objeto de esa clase (es decir, la clase A necesita crear una instancia de la clase B).

En pocas palabras, la inyección de dependencia es una instancia de un objeto A que necesita (depende de) otro objeto B. La instancia del objeto B debe inyectarse en el objeto A, y el programador realiza manualmente esta acción de "inyección" o la entrega al marco de software para su ejecución.

Por lo tanto, descargar la tarea de crear objetos a otra persona y usar las dependencias directamente se denomina inyección de dependencia (DI).

Aquí hay otro ejemplo.

Supongamos que tenemos una clase de automóvil que contiene varios objetos como ruedas, motor, etc. Cuando no se usa la inyección de dependencia, solo podemos crear un nuevo motor (instancia) en el automóvil (clase): motor de valor privado = Motor ():

class Car {
    
    <!-- -->
    private val engine = Engine()

    fun start() {
    
    <!-- -->
        engine.start()
    }
}

fun main(args: Array) {
    
    <!-- -->
    val car = Car()
    car.start()
}

Hay un problema al escribir de esta manera: la clase Car está fuertemente acoplada con Engine Engine. Cada instancia de la clase Car utiliza una instancia de Engine diferente. Esta forma de "construir el motor" dentro de la clase Car hace que la clase Car sea difícil de probar y mantener. Si necesitamos distinguir los automóviles en automóviles con motor de combustión interna y automóviles eléctricos, solo podemos ir a la modificación interna de la clase Automóvil. Esto viola el "principio abierto y cerrado" del desarrollo de software orientado a objetos.

Entonces, ¿cómo debería ser el código de estilo DI? La clase Car recibe un parámetro de tipo Engine en su constructor:class Car(private val engine: Engine){ ... }

class Car(private val engine: Engine) {
    
    <!-- -->
    fun start() {
    
    <!-- -->
        engine.start()
    }
}

fun main(args: Array) {
    
    <!-- -->
    val engine = Engine()
    val car = Car(engine)
    car.start()
}

De esta manera, la clase Car no necesita "construir un motor" internamente. El tipo de motor que necesita se puede pasar (inyectar) directamente desde el exterior cuando se crea una instancia.

Aquí, podemos decir que la instanciación de la clase Car depende de la instancia de la clase Engine. Pasamos (inyectamos) la instancia de la clase Engine cuando construimos la instancia car de la clase Car (las variables de objeto de instancia generalmente tienen minúsculas primero letras).

Los beneficios de hacerlo también son obvios:

  • La clase Coche es reutilizable, necesita un coche de combustible, pase el motor de combustión interna al instanciar, y cuando necesite un coche eléctrico, pase el motor eléctrico;
  • La capacidad de prueba de la clase Car se ha convertido en una realidad (puede pasar diferentes tipos de FakeEngine para probar Test Double).

2 tipos de DI

Hay básicamente tres tipos de DI:

  • inyección de constructor (inyección de constructor de clase): las dependencias se proporcionan a través del constructor de clase.
  • inyección setter (inyección de campo de clase): el cliente expone un método setter que el inyector utiliza para inyectar dependencias.
  • Inyección de interfaz: la dependencia proporciona un método inyector que puede inyectar dependencias en cualquier cliente que lo pase. Los clientes deben implementar una interfaz que exponga un método setter que acepte dependencias.

El ejemplo anterior es una inyección de constructor (inyección de constructor de clase). El segundo tipo: inyección de setter (inyección de campo de clase), como se muestra en el siguiente fragmento de código lateinit var engine: Engine:

class Car {
    
    <!-- -->
    lateinit var engine: Engine

    fun start() {
    
    <!-- -->
        engine.start()
    }
}

fun main(args: Array) {
    
    <!-- -->
    val car = Car()
    car.engine = Engine()
    car.start()
}

Entonces ahora la responsabilidad del DI es:

  • Crear objeto
  • Saber qué clases necesitan esos objetos
  • y darles todos estos objetos

Si hay algún cambio en los objetos, DI lo investigará, no debería preocuparse por las clases que usan esos objetos. De esa forma, si el objeto cambia en el futuro, es responsabilidad de su DI proporcionar el objeto apropiado a la clase.

Por cierto, tuvimos un blog anterior sobre los principios SOLID (cinco principios básicos de programación y diseño orientado a objetos). El quinto elemento en él, las clases deben depender de la abstracción en lugar de la concreción (en términos simples, codificados). De acuerdo con estos principios, una clase debe centrarse en cumplir con sus responsabilidades, en lugar de crear los objetos necesarios para realizar esas responsabilidades. Aquí es donde entra en juego DI: proporciona a las clases los objetos que necesitan.

3 Pros y contras de DI

Ventajas de las DI:

  • Útil para pruebas unitarias.
  • El código estándar se reduce porque la inicialización de las dependencias la realiza el componente del inyector.
  • Ampliar aplicaciones ahora es más fácil.
  • Ayuda a lograr un acoplamiento débil, que es importante en la programación de aplicaciones.

Desventajas de DI:

  • Es un poco complicado de aprender y puede causar problemas de administración y otros problemas si se usa en exceso.
  • Muchos errores en tiempo de compilación pasan al tiempo de ejecución.
  • Los marcos DI se implementan a través de la reflexión o la programación dinámica. Esto puede dificultar el uso de la automatización IDE, como "Buscar referencias", "Mostrar jerarquía de llamadas" y la refactorización segura.

Bibliotecas y marcos para implementar DI:

  • Primavera (Java)
  • Guía de Google (Java)
  • Daga (Java y Android)
  • Castillo de Windsor (.NET)
  • Unidad (.NET)

A través del análisis anterior, tenemos una impresión relativamente intuitiva de inyección de dependencia (DI). El propósito de la inyección de dependencia (DI) es desacoplar entre códigos. El propósito del desacoplamiento es mejorar la reutilización y la capacidad de prueba del código, y mejorar la reutilización y la capacidad de prueba del código es mejorar la solidez del código. Robusto.

Supongo que te gusta

Origin blog.csdn.net/zyctimes/article/details/129218437
Recomendado
Clasificación