Lección 8.0 :Principios de Programación: SOLID
Principios SOLID en Programación
Introducción
¡Hola a todos! Soy Yuscu Mejía y en esta lección vamos a explorar los principios SOLID en programación. SOLID es un acrónimo que representa cinco principios fundamentales para diseñar software que sea fácil de mantener y extender.
Principios SOLID
Los principios SOLID son los siguientes:
- S: Principio de Responsabilidad Única (Single Responsibility Principle, SRP)
- O: Principio de Abierto/Cerrado (Open/Closed Principle, OCP)
- L: Principio de Sustitución de Liskov (Liskov Substitution Principle, LSP)
- I: Principio de Segregación de Interfaces (Interface Segregation Principle, ISP)
- D: Principio de Inversión de Dependencias (Dependency Inversion Principle, DIP)
Propósito
El propósito de los principios SOLID es crear software que sea fácil de mantener y extender. Aplicar estos principios ayuda a evitar el código “espagueti” y hace que el software sea más modular y flexible.
Problema
El problema que resuelven los principios SOLID es cómo diseñar software que sea fácil de mantener y extender, evitando la creación de código rígido y frágil.
Solución
La solución propuesta por los principios SOLID es seguir un conjunto de prácticas que promueven la creación de software modular, flexible y fácil de mantener.
Analogía en el Mundo Real
Una analogía del mundo real sería el diseño de una máquina modular donde cada componente tiene una única responsabilidad y puede ser reemplazado o extendido sin afectar a otros componentes.
Estructura
La estructura de los principios SOLID incluye los siguientes componentes:
- S: Cada clase debe tener una única responsabilidad.
- O: Las entidades de software deben estar abiertas para la extensión, pero cerradas para la modificación.
- L: Los objetos de una clase derivada deben poder reemplazar a los objetos de la clase base sin alterar el comportamiento del programa.
- I: Los clientes no deben verse obligados a depender de interfaces que no utilizan.
- D: Las dependencias deben estar invertidas, de modo que los módulos de alto nivel no dependan de módulos de bajo nivel, sino de abstracciones.

Aplicabilidad
Los principios SOLID son aplicables cuando se desea diseñar software que sea fácil de mantener y extender.
Pros y Contras
- Pros:
- Promueve el diseño de software modular y flexible.
- Facilita el mantenimiento y la extensión del software.
- Ayuda a evitar el código “espagueti”.
- Contras:
- Puede introducir complejidad adicional en el diseño del software.
- Requiere un mayor esfuerzo inicial para diseñar el software siguiendo estos principios.
Vamos a ver el uso de los Principios SOLID
A continuación, un ejemplo de cómo aplicar los principios SOLID en Python, presentado por Yuscu Mejía:
-
- Principio de Responsabilidad Única:
# Ejemplo del Principio de Responsabilidad Única
# Antes: La clase tiene múltiples responsabilidades
class Usuario:
def __init__(self, nombre, email):
self.nombre = nombre
self.email = email
def guardar_usuario(self):
# Lógica para guardar el usuario en una base de datos
print(f"Usuario {self.nombre} guardado en la base de datos.")
def enviar_email(self, mensaje):
# Lógica para enviar un email
print(f"Enviando email a {self.email}: {mensaje}")
# Después: Separando las responsabilidades
class Usuario:
def __init__(self, nombre, email):
self.nombre = nombre
self.email = email
class GestorUsuario:
def guardar_usuario(self, usuario: Usuario):
# Lógica para guardar el usuario en una base de datos
print(f"Usuario {usuario.nombre} guardado en la base de datos.")
class ServicioEmail:
def enviar_email(self, email, mensaje):
# Lógica para enviar un email
print(f"Enviando email a {email}: {mensaje}")
usuario = Usuario("Juan", "juan@example.com")
gestor = GestorUsuario()
email_service = ServicioEmail()
gestor.guardar_usuario(usuario)
email_service.enviar_email(usuario.email, "Bienvenido a nuestro servicio!")
#En este ejemplo, la clase Usuario antes manejaba tanto la lógica de almacenamiento como la de envío de correos,
#violando el Principio de Responsabilidad Única. Después de la refactorización, las responsabilidades se
#separan en las clases GestorUsuario y ServicioEmail, haciendo que cada clase tenga una única responsabilidad.
-
- Principio de Inversión de Dependencias:
# Ejemplo del Principio de Inversión de Dependencias
# Antes: Las dependencias están directamente acopladas
class ServicioNotificacion:
def enviar_mensaje(self, mensaje):
print(f"Mensaje enviado: {mensaje}")
class Usuario:
def __init__(self):
self.servicio_notificacion = ServicioNotificacion()
def notificar(self, mensaje):
self.servicio_notificacion.enviar_mensaje(mensaje)
usuario = Usuario()
usuario.notificar("Hola Mundo")
# Después: Dependencias invertidas mediante una interfaz
from abc import ABC, abstractmethod
class Notificador(ABC):
@abstractmethod
def enviar_mensaje(self, mensaje):
pass
class ServicioNotificacion(Notificador):
def enviar_mensaje(self, mensaje):
print(f"Mensaje enviado: {mensaje}")
class Usuario:
def __init__(self, notificador: Notificador):
self.notificador = notificador
def notificar(self, mensaje):
self.notificador.enviar_mensaje(mensaje)
servicio_notificacion = ServicioNotificacion()
usuario = Usuario(servicio_notificacion)
usuario.notificar("Hola Mundo")
#En este ejemplo, la clase Usuario antes estaba directamente acoplada a ServicioNotificacion,
#lo que la hacía dependiente de una implementación concreta. Después de aplicar el Principio
#de Inversión de Dependencias, Usuario depende de una abstracción (Notificador), no de una
#implementación concreta, lo que facilita la sustitución de la implementación sin modificar
#la clase Usuario.
-
- Principio de Segregación de Interfaces:
# Ejemplo del Principio de Segregación de Interfaces
# Antes: Una interfaz que es demasiado grande
class Animal:
def volar(self):
pass
def nadar(self):
pass
class Pinguino(Animal):
def volar(self):
raise NotImplementedError("Los pingüinos no pueden volar.")
def nadar(self):
return "El pingüino está nadando."
# Después: Interfaces más pequeñas y específicas
class Volador:
def volar(self):
pass
class Nadador:
def nadar(self):
pass
class Pajaro(Volador):
def volar(self):
return "El pájaro está volando."
class Pinguino(Nadador):
def nadar(self):
return "El pingüino está nadando."
pajaro = Pajaro()
pinguino = Pinguino()
print(pajaro.volar())
print(pinguino.nadar())
#En este ejemplo, la interfaz Animal se divide en dos interfaces más pequeñas y específicas:
#Volador y Nadador. Esto evita que las clases que implementan estas
#interfaces tengan métodos que no usan, cumpliendo con el Principio de
#Segregación de Interfaces.
-
- Principio de Sustitución de Liskov:
# Ejemplo del Principio de Sustitución de Liskov
# Antes: Una clase hija que no puede reemplazar a la clase base sin errores
class Ave:
def volar(self):
return "El ave está volando."
class Pinguino(Ave):
def volar(self):
raise Exception("Los pingüinos no pueden volar.")
def hacer_volar_ave(ave: Ave):
return ave.volar()
ave = Ave()
pinguino = Pinguino()
print(hacer_volar_ave(ave))
# Esto lanzará una excepción
# print(hacer_volar_ave(pinguino))
# Después: Asegurando que las clases hijas puedan sustituir a la clase base sin errores
class Ave(ABC):
@abstractmethod
def moverse(self):
pass
class Pajaro(Ave):
def moverse(self):
return "El pájaro está volando."
class Pinguino(Ave):
def moverse(self):
return "El pingüino está nadando."
def hacer_mover_ave(ave: Ave):
return ave.moverse()
pajaro = Pajaro()
pinguino = Pinguino()
print(hacer_mover_ave(pajaro))
print(hacer_mover_ave(pinguino))
#En este ejemplo, la clase Ave se ha convertido en una clase abstracta con un método abstracto moverse.
#Las subclases Pajaro y Pinguino implementan este método de forma que respeta sus características
#específicas. Así, se asegura que cualquier subclase de Ave pueda ser utilizada en lugar de Ave sin
#causar errores, cumpliendo con el Principio de Sustitución de Liskov.
Práctica
Para practicar, les invito a ver nuestro tutorial en video sobre los principios SOLID en Python, presentado por Yuscu Mejía:
Recorda si te gusta los videos, te agradeceria si le pones un comentario o un like para saber si subir mas videos! 😮
Si no sabes que comentar, comenta con: estoy avanzando rapido con el curso de Programacion python 🙂 asi te detectaremos que viniste de aca, para una estadistica si conviene o no subir videos

Ejercicio
Ahora les propongo un ejercicio para aplicar lo que hemos aprendido sobre los principios SOLID en Python, con la guía de Yuscu Mejía:
Ejercicio: Implementación de Principios SOLID
En este ejercicio, vamos a diseñar un sistema para gestionar usuarios y productos en una tienda en línea aplicando los principios SOLID.
Sigue estos pasos para completar el ejercicio:
- Aplica el Principio de Responsabilidad Única para separar las responsabilidades de gestión de usuarios y productos.
- Aplica el Principio de Abierto/Cerrado para permitir la extensión de las funcionalidades de la tienda sin modificar el código existente.
- Aplica el Principio de Sustitución de Liskov para asegurarte de que las clases derivadas puedan sustituir a las clases base.
- Aplica el Principio de Segregación de Interfaces para crear interfaces específicas para los diferentes tipos de clientes.
- Aplica el Principio de Inversión de Dependencias para depender de abstracciones en lugar de concreciones.
# Ejercicio Original
# Principio de Responsabilidad Única
class Usuario:
def __init__(self, nombre):
self.nombre = nombre
def guardar(self):
print(f"Usuario {self.nombre} guardado en la base de datos.")
class Producto:
def __init__(self, nombre, precio):
self.nombre = nombre
self.precio = precio
def aplicar_descuento(self, descuento):
self.precio -= descuento
print(f"Descuento aplicado: {self.precio}")
# Principio de Abierto/Cerrado
class Producto:
def __init__(self, nombre, precio):
self.nombre = nombre
self.precio = precio
def aplicar_descuento(self, tipo):
if tipo == "fijo":
self.precio -= 5
elif tipo == "porcentaje":
self.precio *= 0.9
print(f"Descuento aplicado: {self.precio}")
# Principio de Sustitución de Liskov
class Producto:
def __init__(self, nombre, precio):
self.nombre = nombre
self.precio = precio
class ProductoDigital(Producto):
def __init__(self, nombre, precio, tamano_archivo):
super().__init__(nombre, precio)
self.tamano_archivo = tamano_archivo
# Principio de Segregación de Interfaces
class Producto:
def __init__(self, nombre, precio):
self.nombre = nombre
self.precio = precio
def enviar(self):
print(f"Producto {self.nombre} enviado.")
class ProductoFisico(Producto):
def __init__(self, nombre, precio, peso):
super().__init__(nombre, precio)
self.peso = peso
# Principio de Inversión de Dependencias
class Tienda:
def __init__(self):
self.servicio_notificacion = ServicioNotificacion()
def nueva_venta(self, producto):
self.servicio_notificacion.enviar_notificacion(f"Nuevo producto vendido: {producto.nombre}")
class ServicioNotificacion:
def enviar_notificacion(self, mensaje):
print(f"Enviando notificación: {mensaje}")
# Ejemplo de uso
if __name__ == "__main__":
usuario = Usuario("Juan")
usuario.guardar()
producto = Producto("Laptop", 1000)
producto.aplicar_descuento("fijo")
producto_digital = ProductoDigital("Ebook", 30, "5MB")
print(producto_digital.nombre, producto_digital.precio, producto_digital.tamano_archivo)
producto_fisico = ProductoFisico("Mesa", 150, 30)
producto_fisico.enviar()
tienda = Tienda()
tienda.nueva_venta(producto)
Veamos cómo Yuscu Mejía implementaría la solución a este ejercicio:
#Ejercicio Resuelto
# Principio de Responsabilidad Única
class Usuario:
def __init__(self, nombre):
self.nombre = nombre
class GestionUsuarios:
def agregar_usuario(self, usuario):
print(f"Usuario {usuario.nombre} agregado.")
class Producto:
def __init__(self, nombre, precio):
self.nombre = nombre
self.precio = precio
class GestionProductos:
def agregar_producto(self, producto):
print(f"Producto {producto.nombre} agregado.")
# Principio de Abierto/Cerrado
class Descuento:
def aplicar_descuento(self, producto):
pass
class DescuentoFijo(Descuento):
def aplicar_descuento(self, producto):
producto.precio -= 5
print(f"Descuento fijo aplicado: {producto.precio}")
class DescuentoPorcentaje(Descuento):
def aplicar_descuento(self, producto):
producto.precio *= 0.9
print(f"Descuento por porcentaje aplicado: {producto.precio}")
# Principio de Sustitución de Liskov
class ProductoDigital(Producto):
def __init__(self, nombre, precio, tamano_archivo):
super().__init__(nombre, precio)
self.tamano_archivo = tamano_archivo
# Principio de Segregación de Interfaces
class Enviable:
def enviar(self):
pass
class ProductoFisico(Producto, Enviable):
def __init__(self, nombre, precio, peso):
super().__init__(nombre, precio)
self.peso = peso
def enviar(self):
print(f"Producto {self.nombre} enviado.")
# Principio de Inversión de Dependencias
class ServicioNotificacion:
def enviar_notificacion(self, mensaje):
pass
class EmailNotificacion(ServicioNotificacion):
def enviar_notificacion(self, mensaje):
print(f"Enviando email: {mensaje}")
class Tienda:
def __init__(self, servicio_notificacion: ServicioNotificacion):
self.servicio_notificacion = servicio_notificacion
def nueva_venta(self, producto):
self.servicio_notificacion.enviar_notificacion(f"Nuevo producto vendido: {producto.nombre}")
# Ejemplo de uso
if __name__ == "__main__":
# Gestión de usuarios y productos
gestion_usuarios = GestionUsuarios()
gestion_productos = GestionProductos()
usuario = Usuario("Juan")
producto = Producto("Laptop", 1000)
gestion_usuarios.agregar_usuario(usuario)
gestion_productos.agregar_producto(producto)
# Aplicación de descuentos
descuento_fijo = DescuentoFijo()
descuento_fijo.aplicar_descuento(producto)
descuento_porcentaje = DescuentoPorcentaje()
descuento_porcentaje.aplicar_descuento(producto)
# Envío de producto físico
producto_fisico = ProductoFisico("Mesa", 150, 30)
producto_fisico.enviar()
# Notificación de nueva venta
email_notificacion = EmailNotificacion()
tienda = Tienda(email_notificacion)
tienda.nueva_venta(producto)

