Imagen destacada del tutorial: Principios SOLID de Diseño de Software
Patrones de Diseño

Principios SOLID de Diseño de Software

José Elías Romero Guanipa
01 Sep 2025

Aprende los principios SOLID de diseño de software (SRP, OCP, LSP, ISP, DIP) con ejemplos prácticos en Python y mejora la calidad de tu código.

solid principios diseño srp ocp lsp +5 más

¡Domina los principios SOLID de diseño de software! En este tutorial especializado te guiaré paso a paso para que aprendas los cinco principios fundamentales que te ayudarán a crear software mantenible, extensible y de alta calidad, con ejemplos prácticos en Python.

Objetivo: Aprender y aplicar los principios SOLID (SRP, OCP, LSP, ISP, DIP) en el diseño de software, entender sus beneficios, y saber cuándo y cómo aplicarlos en proyectos reales.

Índice

Paso 1: ¿Qué son los principios SOLID?

Los principios SOLID son cinco principios de diseño orientado a objetos que ayudan a crear software más mantenible, flexible y entendible. Fueron introducidos por Robert C. Martin (Uncle Bob) y se han convertido en estándares fundamentales para el desarrollo de software de calidad.

Los 5 principios SOLID

Principio Significado Descripción
SRP Single Responsibility Principle Una clase debe tener una sola razón para cambiar
OCP Open/Closed Principle Las clases deben estar abiertas para extensión pero cerradas para modificación
LSP Liskov Substitution Principle Las subclases deben ser sustituibles por sus clases base
ISP Interface Segregation Principle Muchas interfaces específicas son mejores que una interfaz general
DIP Dependency Inversion Principle Depender de abstracciones, no de implementaciones concretas

Beneficios de aplicar SOLID

  • Mantenibilidad: Código más fácil de entender y modificar
  • Extensibilidad: Fácil agregar nuevas funcionalidades
  • Testabilidad: Código más fácil de probar unitariamente
  • Reusabilidad: Componentes que pueden ser reutilizados
  • Flexibilidad: Fácil adaptación a cambios de requisitos

Paso 2: SRP - Principio de Responsabilidad Única

El Principio de Responsabilidad Única establece que una clase debe tener una y solo una razón para cambiar. Esto significa que una clase debe ocuparse de una única funcionalidad o responsabilidad.

Ejemplo violando SRP

class Usuario:
    def __init__(self, nombre, email):
        self.nombre = nombre
        self.email = email

    def guardar_en_bd(self):
        # Lógica para guardar en base de datos
        print(f"Guardando {self.nombre} en BD")

    def enviar_email(self, mensaje):
        # Lógica para enviar email
        print(f"Enviando email a {self.email}: {mensaje}")

    def validar_email(self):
        # Lógica de validación de email
        if "@" not in self.email:
            raise ValueError("Email inválido")
        return True

# ❌ Problema: La clase Usuario tiene múltiples responsabilidades:
# 1. Representar datos de usuario
# 2. Persistencia en BD
# 3. Envío de emails
# 4. Validación de emails

Refactorizando aplicando SRP

# ✅ Aplicando SRP: Separar responsabilidades en clases distintas

class Usuario:
    def __init__(self, nombre, email):
        self.nombre = nombre
        self.email = email

class ValidadorUsuario:
    @staticmethod
    def validar_email(email):
        if "@" not in email:
            raise ValueError("Email inválido")
        return True

    @staticmethod
    def validar_nombre(nombre):
        if len(nombre.strip()) == 0:
            raise ValueError("Nombre no puede estar vacío")
        return True

class RepositorioUsuario:
    def guardar(self, usuario):
        # Lógica para guardar en base de datos
        print(f"Guardando {usuario.nombre} en BD")

    def obtener_por_id(self, id):
        # Lógica para obtener usuario
        pass

class ServicioEmail:
    def enviar(self, destinatario, mensaje):
        # Lógica para enviar email
        print(f"Enviando email a {destinatario}: {mensaje}")

# Uso
usuario = Usuario("Ana García", "[email protected]")

validador = ValidadorUsuario()
validador.validar_email(usuario.email)
validador.validar_nombre(usuario.nombre)

repositorio = RepositorioUsuario()
repositorio.guardar(usuario)

servicio_email = ServicioEmail()
servicio_email.enviar(usuario.email, "Bienvenida al sistema")

Casos de uso comunes para SRP

  • Separar lógica de negocio de persistencia
  • Separar validación de modelos de datos
  • Separar lógica de presentación de lógica de aplicación
  • Separar configuración de ejecución

Paso 3: OCP - Principio Abierto/Cerrado

El Principio Abierto/Cerrado establece que las clases deben estar abiertas para extensión pero cerradas para modificación. Podemos agregar nuevas funcionalidades sin modificar el código existente.

Ejemplo violando OCP

class ProcesadorPagos:
    def procesar_pago(self, metodo, monto):
        if metodo == "tarjeta":
            return self._procesar_tarjeta(monto)
        elif metodo == "paypal":
            return self._procesar_paypal(monto)
        elif metodo == "bitcoin":
            return self._procesar_bitcoin(monto)
        else:
            raise ValueError("Método de pago no soportado")

    def _procesar_tarjeta(self, monto):
        return f"Procesando ${monto} con tarjeta"

    def _procesar_paypal(self, monto):
        return f"Procesando ${monto} con PayPal"

    def _procesar_bitcoin(self, monto):
        return f"Procesando ${monto} con Bitcoin"

# ❌ Problema: Cada vez que agregamos un nuevo método de pago,
# tenemos que modificar la clase ProcesadorPagos

Refactorizando aplicando OCP

from abc import ABC, abstractmethod

# ✅ Aplicando OCP: Usar abstracciones y herencia

class MetodoPago(ABC):
    @abstractmethod
    def procesar(self, monto) -> str:
        pass

class TarjetaCredito(MetodoPago):
    def procesar(self, monto) -> str:
        return f"Procesando ${monto} con tarjeta de crédito"

class PayPal(MetodoPago):
    def procesar(self, monto) -> str:
        return f"Procesando ${monto} con PayPal"

class Bitcoin(MetodoPago):
    def procesar(self, monto) -> str:
        return f"Procesando ${monto} with Bitcoin"

class ProcesadorPagos:
    def procesar_pago(self, metodo: MetodoPago, monto):
        return metodo.procesar(monto)

# Ahora podemos agregar nuevos métodos de pago SIN modificar ProcesadorPagos
class TransferenciaBancaria(MetodoPago):
    def procesar(self, monto) -> str:
        return f"Procesando ${monto} con transferencia bancaria"

# Uso
procesador = ProcesadorPagos()

metodos = [
    TarjetaCredito(),
    PayPal(),
    Bitcoin(),
    TransferenciaBancaria()  # ✅ Nuevo método sin modificar existentes
]

monto = 100.50
for metodo in metodos:
    resultado = procesador.procesar_pago(metodo, monto)
    print(resultado)

Estrategias para aplicar OCP

  • Usar herencia y polimorfismo
  • Implementar patrones Strategy o Template Method
  • Usar composición sobre herencia
  • Crear interfaces abstractas

Paso 4: LSP - Principio de Sustitución de Liskov

El Principio de Sustitución de Liskov establece que los objetos de una superclase deben ser reemplazables por objetos de una subclase sin alterar las propiedades del programa.

Ejemplo violando LSP

class Rectangulo:
    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto

    def calcular_area(self):
        return self.ancho * self.alto

class Cuadrado(Rectangulo):
    def __init__(self, lado):
        super().__init__(lado, lado)

    # ❌ Violación LSP: Cuadrado cambia el comportamiento esperado
    @property
    def ancho(self):
        return self._ancho

    @ancho.setter
    def ancho(self, value):
        self._ancho = value
        self._alto = value  # Forzar que alto sea igual a ancho

    @property
    def alto(self):
        return self._alto

    @alto.setter
    def alto(self, value):
        self._alto = value
        self._ancho = value  # Forzar que ancho sea igual a alto

def probar_rectangulo(rect: Rectangulo):
    rect.ancho = 5
    rect.alto = 4
    area_esperada = 20
    area_real = rect.calcular_area()

    if area_real != area_esperada:
        raise ValueError(f"LSP violado: Esperado {area_esperada}, obtenido {area_real}")

    print("✅ LSP cumplido")

# Uso que demuestra la violación
try:
    rectangulo = Rectangulo(5, 4)
    probar_rectangulo(rectangulo)  # ✅ Funciona

    cuadrado = Cuadrado(5)
    probar_rectangulo(cuadrado)    # ❌ Falla - LSP violado
except ValueError as e:
    print(e)

Refactorizando aplicando LSP

from abc import ABC, abstractmethod

# ✅ Aplicando LSP: Diseñar jerarquías correctas

class Forma(ABC):
    @abstractmethod
    def calcular_area(self):
        pass

class Rectangulo(Forma):
    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto

    def calcular_area(self):
        return self.ancho * self.alto

class Cuadrado(Forma):
    def __init__(self, lado):
        self.lado = lado

    def calcular_area(self):
        return self.lado * self.lado

# Ahora ambas clases implementan Forma correctamente
def probar_area(forma: Forma, area_esperada):
    area_real = forma.calcular_area()

    if abs(area_real - area_esperada) > 0.001:
        raise ValueError(f"Área incorrecta: Esperado {area_esperada}, obtenido {area_real}")

    print(f"✅ Área correcta: {area_real}")

# Uso
rectangulo = Rectangulo(5, 4)
cuadrado = Cuadrado(5)

probar_area(rectangulo, 20)  # ✅
probar_area(cuadrado, 25)    # ✅

# Ambas formas son sustituibles y se comportan correctamente

Reglas para cumplir LSP

  • Mismos métodos: Las subclases deben implementar todos los métodos de la superclase
  • Mismas precondiciones: Las subclases no deben fortalecer las precondiciones
  • Mismas postcondiciones: Las subclases no deben debilitar las postcondiciones
  • Mismas invariantes: Las subclases deben preservar las invariantes

Paso 5: ISP - Principio de Segregación de Interfaces

El Principio de Segregación de Interfaces establece que es mejor tener muchas interfaces específicas que una interfaz general. Los clientes no deben verse forzados a depender de interfaces que no usan.

Ejemplo violando ISP

class Dispositivo:
    def imprimir(self, documento):
        raise NotImplementedError

    def escanear(self, documento):
        raise NotImplementedError

    def fax(self, documento):
        raise NotImplementedError

class ImpresoraAntigua(Dispositivo):
    def imprimir(self, documento):
        print(f"Imprimiendo: {documento}")

    def escanear(self, documento):
        raise NotImplementedError("Esta impresora no puede escanear")

    def fax(self, documento):
        raise NotImplementedError("Esta impresora no puede fax")

class ImpresoraMultifuncional(Dispositivo):
    def imprimir(self, documento):
        print(f"Imprimiendo: {documento}")

    def escanear(self, documento):
        print(f"Escaneando: {documento}")

    def fax(self, documento):
        print(f"Enviando fax: {documento}")

# ❌ Problema: ImpresoraAntigua debe implementar métodos que no usa

Refactorizando aplicando ISP

from abc import ABC, abstractmethod

# ✅ Aplicando ISP: Interfaces específicas y pequeñas

class Imprimible(ABC):
    @abstractmethod
    def imprimir(self, documento):
        pass

class Escaneable(ABC):
    @abstractmethod
    def escanear(self, documento):
        pass

class Faxeable(ABC):
    @abstractmethod
    def fax(self, documento):
        pass

class ImpresoraAntigua(Imprimible):
    def imprimir(self, documento):
        print(f"Imprimiendo: {documento}")

class Escaner(Escaneable):
    def escanear(self, documento):
        print(f"Escaneando: {documento}")

class ImpresoraMultifuncional(Imprimible, Escaneable, Faxeable):
    def imprimir(self, documento):
        print(f"Imprimiendo: {documento}")

    def escanear(self, documento):
        print(f"Escaneando: {documento}")

    def fax(self, documento):
        print(f"Enviando fax: {documento}")

# Ahora cada clase sólo implementa lo que necesita
def usar_impresora(impresora: Imprimible, documento):
    impresora.imprimir(documento)

def usar_escaner(escaner: Escaneable, documento):
    escaner.escanear(documento)

# Uso
impresora_vieja = ImpresoraAntigua()
impresora_multifuncional = ImpresoraMultifuncional()
escaner = Escaner()

documento = "Mi documento importante"

usar_impresora(impresora_vieja, documento)
usar_impresora(impresora_multifuncional, documento)
usar_escaner(escaner, documento)
usar_escaner(impresora_multifuncional, documento)  # ✅ También funciona

Beneficios de ISP

  • Menos acoplamiento: Clientes dependen sólo de lo que necesitan
  • Mejor mantenibilidad: Cambios en una interfaz afectan menos código
  • Mayor cohesión: Interfaces más enfocadas y específicas
  • Mejor testing: Más fácil mockear interfaces pequeñas

Paso 6: DIP - Principio de Inversión de Dependencias

El Principio de Inversión de Dependencias establece que los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.

Ejemplo violando DIP

# Módulos de bajo nivel
class BaseDatosMySQL:
    def guardar(self, datos):
        print(f"Guardando en MySQL: {datos}")

    def obtener(self, id):
        print(f"Obteniendo de MySQL: {id}")

class ServicioEmail:
    def enviar(self, destinatario, mensaje):
        print(f"Enviando email a {destinatario}: {mensaje}")

# Módulo de alto nivel
class ServicioUsuarios:
    def __init__(self):
        self.db = BaseDatosMySQL()  # ❌ Dependencia concreta
        self.email = ServicioEmail()  # ❌ Dependencia concreta

    def registrar_usuario(self, nombre, email):
        # Lógica de negocio
        usuario = {"nombre": nombre, "email": email}

        # Depende directamente de implementaciones concretas
        self.db.guardar(usuario)
        self.email.enviar(email, "Bienvenido al sistema")

        return usuario

# ❌ Problema: ServicioUsuarios está fuertemente acoplado a implementaciones específicas

Refactorizando aplicando DIP

from abc import ABC, abstractmethod

# ✅ Aplicando DIP: Depender de abstracciones

# Abstracciones (interfaces)
class Repositorio(ABC):
    @abstractmethod
    def guardar(self, datos):
        pass

    @abstractmethod
    def obtener(self, id):
        pass

class ServicioNotificaciones(ABC):
    @abstractmethod
    def enviar(self, destinatario, mensaje):
        pass

# Implementaciones concretas de bajo nivel
class BaseDatosMySQL(Repositorio):
    def guardar(self, datos):
        print(f"Guardando en MySQL: {datos}")

    def obtener(self, id):
        print(f"Obteniendo de MySQL: {id}")
        return {"id": id, "nombre": "Usuario Ejemplo"}

class BaseDatosPostgreSQL(Repositorio):
    def guardar(self, datos):
        print(f"Guardando en PostgreSQL: {datos}")

    def obtener(self, id):
        print(f"Obteniendo de PostgreSQL: {id}")
        return {"id": id, "nombre": "Usuario Ejemplo"}

class ServicioEmail(ServicioNotificaciones):
    def enviar(self, destinatario, mensaje):
        print(f"Enviando email a {destinatario}: {mensaje}")

class ServicioSMS(ServicioNotificaciones):
    def enviar(self, destinatario, mensaje):
        print(f"Enviando SMS a {destinatario}: {mensaje}")

# Módulo de alto nivel
class ServicioUsuarios:
    def __init__(self, repositorio: Repositorio, notificador: ServicioNotificaciones):
        self.repositorio = repositorio  # ✅ Dependencia abstracta
        self.notificador = notificador  # ✅ Dependencia abstracta

    def registrar_usuario(self, nombre, email):
        usuario = {"nombre": nombre, "email": email}

        self.repositorio.guardar(usuario)
        self.notificador.enviar(email, "Bienvenido al sistema")

        return usuario

# Configuración de dependencias (Inyección de Dependencias)
def configurar_aplicacion():
    # Podemos cambiar fácilmente las implementaciones
    repositorio = BaseDatosPostgreSQL()  # o BaseDatosMySQL()
    notificador = ServicioSMS()          # o ServicioEmail()

    return ServicioUsuarios(repositorio, notificador)

# Uso
servicio = configurar_aplicacion()
usuario = servicio.registrar_usuario("Ana García", "[email protected]")
print(f"Usuario registrado: {usuario}")

Ventajas de DIP

  • Desacoplamiento: Módulos independientes y reutilizables
  • Flexibilidad: Fácil cambiar implementaciones
  • Testabilidad: Fácil usar mocks en tests
  • Mantenibilidad: Cambios aislados y controlados

Paso 7: Combinando principios SOLID

Los principios SOLID funcionan mejor cuando se aplican en conjunto. Veamos un ejemplo que combina múltiples principios.

Sistema de procesamiento de pedidos

from abc import ABC, abstractmethod
from typing import List, Dict, Any
import uuid

# DIP: Abstracciones
class RepositorioPedidos(ABC):
    @abstractmethod
    def guardar(self, pedido) -> Any:
        pass

    @abstractmethod
    def obtener_por_id(self, id: str) -> Any:
        pass

class ServicioNotificaciones(ABC):
    @abstractmethod
    def enviar(self, destinatario: str, mensaje: str):
        pass

class CalculadorDescuentos(ABC):
    @abstractmethod
    def calcular_descuento(self, total: float) -> float:
        pass

# ISP: Interfaces específicas
class ValidadorPedido(ABC):
    @abstractmethod
    def validar(self, pedido) -> bool:
        pass

# Implementaciones concretas
class RepositorioPedidosMySQL(RepositorioPedidos):
    def guardar(self, pedido) -> Any:
        print(f"Guardando pedido {pedido['id']} en MySQL")
        return pedido

    def obtener_por_id(self, id: str) -> Any:
        print(f"Obteniendo pedido {id} desde MySQL")
        return {"id": id, "estado": "procesado"}

class ServicioEmail(ServicioNotificaciones):
    def enviar(self, destinatario: str, mensaje: str):
        print(f"Enviando email a {destinatario}: {mensaje}")

class CalculadorDescuentosEstándar(CalculadorDescuentos):
    def calcular_descuento(self, total: float) -> float:
        if total > 1000:
            return total * 0.1  # 10% de descuento
        elif total > 500:
            return total * 0.05  # 5% de descuento
        return 0

class ValidadorStock(ValidadorPedido):
    def __init__(self, inventario: Dict[str, int]):
        self.inventario = inventario

    def validar(self, pedido) -> bool:
        for item in pedido['items']:
            producto_id = item['producto_id']
            cantidad = item['cantidad']
            if self.inventario.get(producto_id, 0) < cantidad:
                raise ValueError(f"Stock insuficiente para producto {producto_id}")
        return True

# SRP: Cada clase tiene una responsabilidad clara
class ProcesadorPedidos:
    def __init__(self, 
                 repositorio: RepositorioPedidos,
                 notificador: ServicioNotificaciones,
                 calculador_descuentos: CalculadorDescuentos,
                 validadores: List[ValidadorPedido]):
        self.repositorio = repositorio
        self.notificador = notificador
        self.calculador_descuentos = calculador_descuentos
        self.validadores = validadores

    # OCP: Fácil extender con nuevos validadores
    def agregar_validador(self, validador: ValidadorPedido):
        self.validadores.append(validador)

    def procesar_pedido(self, usuario: Dict[str, Any], items: List[Dict[str, Any]]) -> Dict[str, Any]:
        # Crear pedido
        pedido = {
            'id': str(uuid.uuid4()),
            'usuario': usuario,
            'items': items,
            'estado': 'pendiente'
        }

        # Validar pedido (OCP: nuevos validadores se agregan fácilmente)
        for validador in self.validadores:
            validador.validar(pedido)

        # Calcular total
        total = sum(item['precio'] * item['cantidad'] for item in items)
        descuento = self.calculador_descuentos.calcular_descuento(total)
        total_con_descuento = total - descuento

        pedido['total'] = total
        pedido['descuento'] = descuento
        pedido['total_con_descuento'] = total_con_descuento
        pedido['estado'] = 'procesado'

        # Guardar y notificar
        pedido_guardado = self.repositorio.guardar(pedido)
        self.notificador.enviar(usuario['email'], f"Pedido {pedido['id']} procesado. Total: ${total_con_descuento:.2f}")

        return pedido_guardado

# Configuración (DIP: Inyección de dependencias)
def configurar_sistema():
    inventario = {"prod1": 10, "prod2": 5, "prod3": 20}

    repositorio = RepositorioPedidosMySQL()
    notificador = ServicioEmail()
    calculador_descuentos = CalculadorDescuentosEstándar()
    validador_stock = ValidadorStock(inventario)

    procesador = ProcesadorPedidos(repositorio, notificador, calculador_descuentos, [validador_stock])
    return procesador

# Uso
sistema = configurar_sistema()

usuario = {"id": "user1", "nombre": "Ana", "email": "[email protected]"}
items = [
    {"producto_id": "prod1", "nombre": "Laptop", "precio": 1200, "cantidad": 1},
    {"producto_id": "prod2", "nombre": "Mouse", "precio": 45, "cantidad": 2}
]

try:
    pedido = sistema.procesar_pedido(usuario, items)
    print(f"Pedido procesado exitosamente: {pedido['id']}")
    print(f"Total con descuento: ${pedido['total_con_descuento']:.2f}")
except ValueError as e:
    print(f"Error procesando pedido: {e}")

Paso 8: Refactoring aplicando SOLID

Veamos un ejemplo práctico de refactorización aplicando todos los principios SOLID.

Código original con problemas SOLID

class TiendaOnline:
    def __init__(self):
        self.productos = []
        self.usuarios = []

    def agregar_producto(self, nombre, precio):
        self.productos.append({"nombre": nombre, "precio": precio})

    def registrar_usuario(self, nombre, email):
        self.usuarios.append({"nombre": nombre, "email": email})

    def calcular_total_carrito(self, items):
        total = 0
        for item in items:
            total += item["precio"] * item["cantidad"]
        if total > 100:
            total *= 0.9  # Descuento
        return total

    def procesar_pago(self, total, metodo_pago):
        if metodo_pago == "tarjeta":
            return f"Procesando ${total} con tarjeta"
        elif metodo_pago == "paypal":
            return f"Procesando ${total} con PayPal"
        else:
            raise ValueError("
Método de pago no soportado"

    def guardar_en_bd(self, datos):
        # Lógica de base de datos
        print(f"Guardando en BD: {datos}")

    def enviar_email(self, destinatario, mensaje):
        # Lógica de email
        print(f"Enviando email a {destinatario}: {mensaje}")

# ❌ Problemas SOLID:
# - SRP: Múltiples responsabilidades en una clase
# - OCP: Difícil agregar nuevos métodos de pago
# - DIP: Dependencias concretas

Refactorización aplicando SOLID

from abc import ABC, abstractmethod
from typing import List, Dict, Any

# SRP: Modelos separados
class Producto:
    def __init__(self, nombre: str, precio: float):
        self.nombre = nombre
        self.precio = precio

class Usuario:
    def __init__(self, nombre: str, email: str):
        self.nombre = nombre
        self.email = email

# DIP: Abstracciones
class Repositorio(ABC):
    @abstractmethod
    def guardar(self, datos: Any) -> Any:
        pass

class ServicioNotificaciones(ABC):
    @abstractmethod
    def enviar(self, destinatario: str, mensaje: str):
        pass

class MetodoPago(ABC):
    @abstractmethod
    def procesar(self, monto: float) -> str:
        pass

class CalculadorDescuentos(ABC):
    @abstractmethod
    def calcular_descuento(self, total: float) -> float:
        pass

# ISP: Interfaces específicas
class Validador(ABC):
    @abstractmethod
    def validar(self, datos: Any) -> bool:
        pass

# Implementaciones concretas
class RepositorioMySQL(Repositorio):
    def guardar(self, datos: Any) -> Any:
        print(f"Guardando en MySQL: {datos}")
        return datos

class ServicioEmail(ServicioNotificaciones):
    def enviar(self, destinatario: str, mensaje: str):
        print(f"Enviando email a {destinatario}: {mensaje}")

class TarjetaCredito(MetodoPago):
    def procesar(self, monto: float) -> str:
        return f"Procesando ${monto} con tarjeta de crédito"

class PayPal(MetodoPago):
    def procesar(self, monto: float) -> str:
        return f"Procesando ${monto} con PayPal"

class CalculadorDescuentosEstándar(CalculadorDescuentos):
    def calcular_descuento(self, total: float) -> float:
        if total > 100:
            return total * 0.1
        return 0

class ValidadorStock(Validador):
    def __init__(self, inventario: Dict[str, int]):
        self.inventario = inventario

    def validar(self, items: List[Dict[str, Any]]) -> bool:
        for item in items:
            if item["cantidad"] > self.inventario.get(item["producto_id"], 0):
                raise ValueError(f"Stock insuficiente para {item['producto_id']}")
        return True

# OCP: Fácil extensión
class TiendaOnline:
    def __init__(self, 
                 repositorio: Repositorio,
                 notificador: ServicioNotificaciones,
                 calculador_descuentos: CalculadorDescuentos,
                 validadores: List[Validador]):
        self.repositorio = repositorio
        self.notificador = notificador
        self.calculador_descuentos = calculador_descuentos
        self.validadores = validadores
        self.metodos_pago = {}

    def registrar_metodo_pago(self, nombre: str, metodo: MetodoPago):
        self.metodos_pago[nombre] = metodo

    def agregar_validador(self, validador: Validador):
        self.validadores.append(validador)

    def procesar_compra(self, usuario: Usuario, items: List[Dict[str, Any]], metodo_pago: str) -> Dict[str, Any]:
        # Validar
        for validador in self.validadores:
            validador.validar(items)

        # Calcular total
        total = sum(item["precio"] * item["cantidad"] for item in items)
        descuento = self.calculador_descuentos.calcular_descuento(total)
        total_final = total - descuento

        # Procesar pago
        if metodo_pago not in self.metodos_pago:
            raise ValueError(f"Método de pago no soportado: {metodo_pago}")

        resultado_pago = self.metodos_pago[metodo_pago].procesar(total_final)

        # Guardar y notificar
        compra = {
            "usuario": usuario.nombre,
            "items": items,
            "total": total_final,
            "metodo_pago": metodo_pago
        }

        self.repositorio.guardar(compra)
        self.notificador.enviar(usuario.email, f"Compra procesada: ${total_final:.2f}")

        return {
            "compra": compra,
            "resultado_pago": resultado_pago
        }

# Configuración
def configurar_tienda():
    inventario = {"prod1": 10, "prod2": 5}

    repositorio = RepositorioMySQL()
    notificador = ServicioEmail()
    calculador = CalculadorDescuentosEstándar()
    validador_stock = ValidadorStock(inventario)

    tienda = TiendaOnline(repositorio, notificador, calculador, [validador_stock])

    # Registrar métodos de pago (OCP)
    tienda.registrar_metodo_pago("tarjeta", TarjetaCredito())
    tienda.registrar_metodo_pago("paypal", PayPal())

    return tienda

# Uso
tienda = configurar_tienda()

usuario = Usuario("Ana García", "[email protected]")
items = [
    {"producto_id": "prod1", "nombre": "Laptop", "precio": 1200, "cantidad": 1},
    {"producto_id": "prod2", "nombre": "Mouse", "precio": 45, "cantidad": 2}
]

try:
    resultado = tienda.procesar_compra(usuario, items, "paypal")
    print("✅ Compra exitosa:")
    print(f"   Total: ${resultado['compra']['total']:.2f}")
    print(f"   {resultado['resultado_pago']}")
except ValueError as e:
    print(f"❌ Error: {e}")

Paso 9: Cuando NO aplicar SOLID

Aunque los principios SOLID son importantes, hay situaciones donde su aplicación estricta puede ser contraproducente.

Casos donde aplicar SOLID con cuidado

  1. Proyectos pequeños y simples

    # ❌ Over-engineering para un script simple
    class CalculadoraSimple:
       def sumar(self, a, b):
           return a + b
    
       def restar(self, a, b):
           return a - b
    
    # ✅ Mejor mantenerlo simple
  2. Prototipos y pruebas de concepto

    # Durante el prototyping, focus en funcionalidad, no en arquitectura
    class PrototipoRapido:
       def hacer_todo(self):
           # Código rápido y sucio para validar idea
           pass
  3. Performance crítica

    # En algunos casos, las abstracciones pueden afectar el performance
    # Evaluar trade-off entre clean code y performance
  4. Cuando añade complejidad innecesaria

    # Si el beneficio no justifica la complejidad añadida
    # Mantener el principio KISS (Keep It Simple, Stupid)

Regla general

Aplica SOLID cuando:

  • El proyecto tiene vida larga
  • Requiere mantenimiento frecuente
  • Tiene múltiples desarrolladores
  • Necesita alta testabilidad

Considera alternativas cuando:

  • Es un proyecto pequeño/script
  • Es un prototipo temporal
  • El performance es crítico
  • La simplicidad es más importante

Conclusión

¡Has dominado los principios SOLID de diseño de software! Estos principios te proporcionan un framework sólido para crear software mantenible, extensible y de alta calidad.

Recuerda que SOLID son guías, no reglas absolutas. La clave está en entender el contexto y aplicar estos principios de manera pragmática.

Practica aplicando estos principios en tus proyectos y siempre evalúa el trade-off entre clean code y simplicidad.

Para más tutoriales sobre principios de diseño y patrones, visita nuestra sección de tutoriales.


¡Sigue practicando y aplicando estos principios en proyectos reales!


💡 Tip Importante

📝 Mejores Prácticas con SOLID

  • Empieza simple: No apliques todos los principios desde el inicio
  • Refactoriza incrementalmente: Mejora el código existente aplicando SOLID gradualmente
  • Entiende el contexto: Aplica SOLID según las necesidades del proyecto
  • Mide el impacto: Evalúa si los beneficios justifican la complejidad added
  • Mantén el balance: Encuentra el equilibrio entre SOLID y simplicidad
  • Aprende de ejemplos: Estudia código bien diseñado en proyectos open source
  • Practica constantemente: La maestría viene con la práctica repetida
  • Busca feedback: Comparte tu código con otros desarrolladores

📚 Recursos Recomendados:

¡Estos principios transformarán la forma en que diseñas y construyes software!

Toca los botones para interactuar

Comentarios

Comentarios

Inicia sesión para dejar un comentario.

No hay comentarios aún

Sé el primero en comentar este tutorial.

Tutoriales Relacionados

Descubre más tutoriales relacionados que podrían ser de tu interés

Imagen destacada del tutorial relacionado: Fundamentos de Programación: Patrones de Diseño de Comportamiento
Patrones de Diseño

Fundamentos de Programación: Patrones de Diseño de Comportamiento

Aprende patrones de diseño de comportamiento como Observer, Strategy, Command y State con ejemplos prácticos en Python.

José Elías Romero Guanipa
02 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño Creacionales
Patrones de Diseño

Patrones de Diseño Creacionales

Aprende patrones de diseño creacionales como Singleton, Factory, Builder y Prototype con ejemplos prácticos en Python.

José Elías Romero Guanipa
03 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño Estructurales
Patrones de Diseño

Patrones de Diseño Estructurales

Aprende patrones de diseño estructurales como Adapter, Decorator, Facade y Proxy con ejemplos prácticos en Python.

José Elías Romero Guanipa
04 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño - Aplicaciones Avanzadas
Patrones de Diseño

Patrones de Diseño - Aplicaciones Avanzadas

Aprende aplicaciones avanzadas de patrones de diseño en arquitecturas empresariales como microservicios, CQRS, event sourcing y más con ejemplos prácticos en Python.

José Elías Romero Guanipa
05 Sep 2025
Foto de perfil del autor José Elías Romero Guanipa
José Elías Romero Guanipa
Autor

🌟 Nube de Etiquetas

Descubre temas populares en nuestros tutoriales

python
python 30 tutoriales
poo
poo 8 tutoriales
ciencia de datos
ciencia de datos 8 tutoriales
patrones diseño
patrones diseño 7 tutoriales
matplotlib
matplotlib 7 tutoriales
pandas
pandas 6 tutoriales
visualizacion
visualizacion 6 tutoriales
principiante
principiante 5 tutoriales
numpy
numpy 5 tutoriales
estadistica
estadistica 4 tutoriales
bases de datos
bases de datos 4 tutoriales
dataframe
dataframe 4 tutoriales
csv
csv 3 tutoriales
json
json 3 tutoriales
machine learning
machine learning 3 tutoriales
rendimiento
rendimiento 3 tutoriales
mysql
mysql 3 tutoriales
postgresql
postgresql 3 tutoriales
analisis de datos
analisis de datos 3 tutoriales
graficos
graficos 3 tutoriales
excepciones
excepciones 2 tutoriales
algoritmos
algoritmos 2 tutoriales
estructuras datos
estructuras datos 2 tutoriales
programación
programación 2 tutoriales
colaboracion
colaboracion 2 tutoriales

Las etiquetas más grandes y brillantes aparecen en más tutoriales

logo logo

©2024 ViveBTC