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 elUserRepository
. - Mantenimiento: Facilita el mantenimiento y escalabilidad del código, ya que puedes agregar nuevas fuentes de datos sin afectar otras partes del sistema.