- ¿Qué es el DIP?
- Problema: Código acoplado
- Solución: Separar con una abstracción
- Cambiar de base de datos sin romper tu aplicación
- Formas de inyectar dependencias
- ¿Qué son alto y bajo nivel?
- ¿Cuándo usar DIP?
Ya hemos avanzado bastante en Python, y ahora es momento de aprender un concepto clave para hacer nuestro código más flexible y fácil de mantener: el DIP (Principio de Inversión de Dependencias ).
Este principio es parte de los principios SOLID en programación orientada a objetos. Hoy veremos el último de esos cinco principios.
¿Qué es el DIP?
El principio de inversión de dependencias nos dice dos cosas muy importantes:
- Los módulos de alto nivel (o sea, los que se encargan de la lógica principal de la aplicación) no deben depender directamente de módulos de bajo nivel (los que hacen tareas específicas como acceder a una base de datos).
- Tanto los de alto nivel como los de bajo nivel deben depender de una abstracción (por ejemplo, una clase base o una interfaz).
¿Por qué hacemos esto? Porque si todo depende de todo, cualquier cambio en una parte de nuestro sistema puede romper otra. En cambio, si usamos abstracciones, es más fácil cambiar detalles sin modificar lo demás. Veámoslo con un ejemplo concreto.
Problema: Código acoplado
Imaginemos que tenemos una clase que guarda información del usuario en una base de datos MySQL:
class MySQLDatabase:
def save(self, data):
print(f"Guardando {data} en base de datos MySQL")
class User:
def __init__(self):
self.database = MySQLDatabase()
def save_user(self, data):
self.database.save(data)
Aquí el problema es que la clase User está directamente conectada a la clase MySQLDatabase. Entonces, ¿qué pasa si mañana queremos usar otra base de datos como MongoDB? Toca cambiar la clase User, lo cual puede ser riesgoso o costoso, sobre todo si ya está en producción.
Solución: Separar con una abstracción
Ahora vamos a aplicar DIP creando una abstracción que representa una base de datos genérica:
class Database:
def save(self, data):
pass # Solo la interfaz, se implementará después
class MySQLDatabase(Database):
def save(self, data):
print(f"Guardando {data} en base de datos MySQL")
class User:
def __init__(self, database):
self.database = database
def save_user(self, data):
self.database.save(data)
Con esto, User ya no depende directamente de MySQLDatabase. Ahora lo que necesita es algo que se comporte como Database, sin importar cómo esté implementado por dentro. Es decir, usamos una abstracción como puente.
Cambiar de base de datos sin romper tu aplicación
Digamos que queremos usar MongoDB. Solo necesitamos crear una nueva clase:
class MongoDBDatabase(Database):
def save(self, data):
print(f"Guardando {data} en base de datos MongoDB")
# Podemos cambiar la base de datos sin tocar la clase User
user = User(MongoDBDatabase())
user.save_user("datos del usuario")
¡Listo! No tocamos nada en la clase User. Gracias a DIP, nuestro código es más flexible y fácil de modificar o extender.
Formas de inyectar dependencias
Este tipo de diseño se basa en algo que llamamos inyección de dependencias, que básicamente es una forma de pasarle al código los objetos que necesita. Existen tres formas muy comunes de hacerlo:
1. Inyección por parámetro de función
Muy útil cuando el uso es puntual:
def guardar_usuario(data, database):
database.save(data)
guardar_usuario("usuario 1", MySQLDatabase())
2. Inyección por el constructor
Cuando necesitamos mantener la dependencia durante todo el ciclo de vida del objeto:
class Aplicacion:
def __init__(self, logger):
self.logger = logger
app = Aplicacion(Logger())
3. Inyección por setter (método aparte)
No es muy recomendable, ya que puede dejar objetos en un estado inconsistente si olvidamos llamar al método set_logger.
class Aplicacion:
def set_logger(self, logger):
self.logger = logger
app = Aplicacion()
app.set_logger(Logger())
¿Qué son alto y bajo nivel?
| Tipo de módulo | ¿Qué hace? | Ejemplo |
|---|---|---|
| Alto nivel | Contiene lógica del negocio | Clase User, clase Aplicacion |
| Bajo nivel | Se encarga de detalles técnicos | Clase MySQLDatabase, Logger |
| Abstracción | Define cómo se comunican | Clase Database, interfaz |
¿Cuándo usar DIP?
No es necesario aplicar este principio en todo. Usémoslo cuando tengamos módulos que:
- Pueden cambiar en el tiempo
- Queremos probar fácilmente (por ejemplo, usando versiones simuladas o mocks)
- Requieren una arquitectura escalable y mantenible
Resumen
- DIP (principio de inversión de dependencias) permite que los módulos de alto nivel no dependan directamente de los de bajo nivel.
- En su lugar, ambos deben depender de una abstracción (interfaz o clase base).
- Esto hace que tu código sea más flexible, mantenible y fácil de extender.
- Existen tres formas de inyectar dependencias: por parámetro, constructor y setter.
- No apliques DIP por costumbre; úsalo cuando el proyecto realmente lo necesite.
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.