Regístrate para acceder a más de 15 cursos gratuitos de programación con un simulador

Inversión de dependencias Python: Polimorfismo

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.

Obtener acceso
130
cursos
1000
ejercicios
2000+
horas de teoría
3200
test

Obtén acceso

Cursos de programación para principiantes y desarrolladores experimentados. Comienza tu aprendizaje de forma gratuita

  • 130 cursos, 2000+ horas de teoría
  • 1000 ejercicios prácticos en el navegador
  • 360 000 estudiantes
Al enviar el formulario, aceptas el «Política de privacidad» y los términos de la «Oferta», y también aceptas los «Términos y condiciones de uso»

Nuestros graduados trabajan en empresas como:

Bookmate
Health Samurai
Dualboot
ABBYY