Dependency Inversion con Kotlin

Dependency Inversion con Kotlin

Dependency Inversion Principle (DIP) es uno de los principios SOLID y establece que los módulos de alto nivel no deben depender de módulos de bajo nivel, ambos deben depender de abstracciones (interfaces). Las abstracciones no deben depender de los detalles, sino que los detalles deben depender de las abstracciones.


Problema sin aplicar DIP:

Imagina que tienes una clase UserRepository que se encarga de obtener usuarios, y depende directamente de una implementación concreta como LocalDataSource. Si quieres cambiar la fuente de datos a un servidor remoto o una base de datos diferente, tendrías que modificar UserRepository, lo que rompe con el principio DIP.

class LocalDataSource {
    fun getUserData(): String {
        return "User from Local Database"
    }
}

class UserRepository {
    private val localDataSource = LocalDataSource()

    fun getUser(): String {
        return localDataSource.getUserData()
    }
}


Solución aplicando DIP:

Ahora, vamos a introducir una abstracción (una interfaz) que tanto LocalDataSource como otras posibles fuentes de datos (por ejemplo, RemoteDataSource) implementarán, permitiendo que UserRepository dependa de una abstracción y no de una implementación concreta.


Paso 1: Crear la abstracción (interfaz)

interface UserDataSource {
    fun getUserData(): String
}


Paso 2: Implementar diferentes fuentes de datos basadas en la interfaz

class LocalDataSource : UserDataSource {
    override fun getUserData(): String {
        return "User from Local Database"
    }
}

class RemoteDataSource : UserDataSource {
    override fun getUserData(): String {
        return "User from Remote Server"
    }
}


Paso 3: Hacer que UserRepository dependa de la abstracción en lugar de una implementación concreta

class UserRepository(private val userDataSource: UserDataSource) {
    fun getUser(): String {
        return userDataSource.getUserData()
    }
}


Paso 4: Inyectar la dependencia

Puedes inyectar la implementación que prefieras en el constructor de UserRepository. Por ejemplo:

fun main() {
    // Puedes elegir la fuente de datos que quieras
    val localRepository = UserRepository(LocalDataSource())
    println(localRepository.getUser())  // Output: User from Local Database

    val remoteRepository = UserRepository(RemoteDataSource())
    println(remoteRepository.getUser())  // Output: User from Remote Server
}


Beneficios de aplicar DIP:

  • Flexibilidad: Puedes cambiar la implementación de la fuente de datos (de local a remoto, por ejemplo) sin cambiar UserRepository.
  • Testabilidad: Es más fácil realizar pruebas unitarias, ya que puedes inyectar un mock de UserDataSource para probar el UserRepository.
  • Mantenimiento: Facilita el mantenimiento y escalabilidad del código, ya que puedes agregar nuevas fuentes de datos sin afectar otras partes del sistema.

Comentarios

Aún no hay comentarios. ¿Por qué no comienzas el debate?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *