Inyección de Dependencias en Kotlin: guía sencilla para principiantes

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿qué es la Inyección de Dependencias y para qué sirve?

\n

Imagine que está construyendo una casa. Necesita ladrillos, cemento y herramientas. En lugar de ir cada vez al almacén por ellos, simplemente solicita la entrega de todos los materiales en la obra. Dependency Injection (DI) o Inyección de Dependencias es el mismo principio, pero para su código.

\n

La Inyección de Dependencias es un patrón de diseño en el que un objeto recibe sus dependencias desde el exterior, en lugar de crearlas él mismo. Esto hace que el código sea más flexible, comprobable y fácil de mantener. En este artículo analizaremos DI con el ejemplo de Kotlin, un lenguaje de programación moderno que es perfecto para aprender este patrón.

\n\n

El problema: ¿por qué sin DI el código se vuelve frágil?

\n

Consideremos un ejemplo típico. Supongamos que tenemos una clase UserService que envía notificaciones por email. Sin DI se ve así:

\n
class EmailService {\n    fun sendEmail(to: String, message: String) {\n        println("Enviando email a $to: $message")\n    }\n}\n\nclass UserService {\n    private val emailService = EmailService() // ¡Acoplamiento rígido!\n\n    fun notifyUser(email: String) {\n        emailService.sendEmail(email, "¡Hola, usuario!")\n    }\n}
\n

¿Cuál es el problema? UserService está rígidamente vinculado a la implementación concreta de EmailService. Si desea:

\n
    \n
  • Reemplazar EmailService por SmsService;
  • \n
  • Añadir registro de logs;
  • \n
  • Escribir pruebas unitarias con un objeto mock,
  • \n
\n

— tendrá que modificar el código de UserService. Esto viola el principio Open/Closed (abierto/cerrado) y dificulta las pruebas.

\n\n

Solución: inyección manual de dependencias en Kotlin

\n

La forma más sencilla de implementar DI es pasar la dependencia a través del constructor. En Kotlin esto se hace de manera muy concisa:

\n
// 1. Creamos una interfaz (abstracción)\ninterface NotificationService {\n    fun send(to: String, message: String)\n}\n\n// 2. Implementamos servicios concretos\nclass EmailService : NotificationService {\n    override fun send(to: String, message: String) {\n        println("Email a $to: $message")\n    }\n}\n\nclass SmsService : NotificationService {\n    override fun send(to: String, message: String) {\n        println("SMS a $to: $message")\n    }\n}\n\n// 3. Inyectamos la dependencia a través del constructor\nclass UserService(private val notificationService: NotificationService) {\n    fun notifyUser(email: String) {\n        notificationService.send(email, "¡Hola!")\n    }\n}\n\n// 4. Uso\nfun main() {\n    val emailService = EmailService()\n    val userService = UserService(emailService) // ¡Inyección!\n    userService.notifyUser("user@example.com")\n}
\n

Ventajas de este enfoque:

\n
    \n
  • Flexibilidad: se puede reemplazar fácilmente EmailService por SmsService;
  • \n
  • Capacidad de prueba: en las pruebas puede pasar un objeto mock:
  • \n
\n
class MockNotificationService : NotificationService {\n    var lastMessage: String? = null\n    override fun send(to: String, message: String) {\n        lastMessage = message\n    }\n}\n\nfun test() {\n    val mock = MockNotificationService()\n    val userService = UserService(mock)\n    userService.notifyUser("test@test.com")\n    assert(mock.lastMessage == "¡Hola!")\n}
\n\n

Frameworks DI: Koin y Dagger para Kotlin

\n

Con DI manual, si el proyecto es grande, se escribe mucho código repetitivo. Aquí es donde los frameworks DI vienen al rescate. En el mundo de Kotlin, dos son populares: Koin (simple) y Dagger/Hilt (potente).

\n\n

Koin: simplicidad y velocidad

\n

Koin es un framework DI ligero escrito en Kotlin. No requiere generación de código y es

Blogs

Book Recommendations