Skip to content
SP StackPractices
intermediate

Principios SOLID Explicados con Ejemplos

Aprende los cinco principios SOLID con ejemplos prácticos de código: Responsabilidad Única, Abierto/Cerrado, Sustitución de Liskov, Segregación de Interfaces e Inversión de Dependencias.

Temas: design

Principios SOLID Explicados con Ejemplos

Introducción

SOLID es un acrónimo de cinco principios de diseño que hacen los diseños de software más comprensibles, flexibles y mantenibles. Fueron introducidos por Robert C. Martin y son fundamentales para el diseño orientado a objetos.

LetraPrincipioIdea Central
SResponsabilidad ÚnicaUna clase debe tener una única razón para cambiar
OAbierto/CerradoAbierto para extensión, cerrado para modificación
LSustitución de LiskovLos subtipos deben ser sustituibles por sus tipos base
ISegregación de InterfacesLos clientes no deben depender de interfaces que no usan
DInversión de DependenciasDepender de abstracciones, no de concreciones

S — Principio de Responsabilidad Única (SRP)

Una clase debe tener solo una razón para cambiar.

# Malo: una clase maneja lógica de órdenes Y reportes
class OrderManager:
    def create_order(self, items):
        ...
    def cancel_order(self, order_id):
        ...
    def generate_monthly_report(self):
        ...  # preocupación completamente diferente

# Bueno: separar responsabilidades
class OrderService:
    def create_order(self, items):
        ...
    def cancel_order(self, order_id):
        ...

class ReportGenerator:
    def generate_monthly_report(self):
        ...

Por qué importa: Cuando una clase tiene múltiples responsabilidades, cambios en una pueden romper otra. Clases pequeñas y enfocadas son más fáciles de entender, probar y reutilizar.

O — Principio Abierto/Cerrado (OCP)

Las entidades de software deben estar abiertas para extensión pero cerradas para modificación.

# Malo: modificar código existente para cada nuevo método de pago
class PaymentProcessor:
    def process(self, payment):
        if payment.type == "credit_card":
            ...
        elif payment.type == "paypal":
            ...
        elif payment.type == "crypto":  # agregado después
            ...

# Bueno: extender vía nuevas clases
class PaymentMethod(ABC):
    @abstractmethod
    def process(self, amount):
        pass

class CreditCardPayment(PaymentMethod):
    def process(self, amount):
        ...

class PayPalPayment(PaymentMethod):
    def process(self, amount):
        ...

class PaymentProcessor:
    def __init__(self, method: PaymentMethod):
        self.method = method

    def process(self, amount):
        self.method.process(amount)

# Agregar un nuevo método requiere cero cambios en código existente
class CryptoPayment(PaymentMethod):
    def process(self, amount):
        ...

Por qué importa: Modificar código existente que funciona introduce riesgo. Al extender mediante nuevo código, preservas la estabilidad de lo que ya funciona.

L — Principio de Sustitución de Liskov (LSP)

Los objetos de una superclase deben ser reemplazables por objetos de sus subclases sin romper el programa.

# Malo: Cuadrado viola LSP cuando se usa como Rectángulo
class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height

    def set_width(self, w):
        self._width = w

    def set_height(self, h):
        self._height = h

    def area(self):
        return self._width * self._height

class Square(Rectangle):  # viola LSP
    def set_width(self, w):
        self._width = w
        self._height = w  # ¡efecto secundario sorprendente!

    def set_height(self, h):
        self._width = h   # ¡efecto secundario sorprendente!
        self._height = h

# Una función esperando comportamiento de Rectángulo se rompe con Cuadrado

def resize_rectangle(rect: Rectangle):
    rect.set_width(5)
    rect.set_height(4)
    assert rect.area() == 20  # ¡falla para Cuadrado!
# Bueno: modelar Cuadrado independientemente o como value object
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

@dataclass(frozen=True)
class Square:
    side: int

    def area(self):
        return self.side * self.side

Por qué importa: Violar LSP conduce a bugs sutiles cuando se usa polimorfismo. La subclase debe honrar el contrato de la clase padre.

I — Principio de Segregación de Interfaces (ISP)

Los clientes no deben verse forzados a depender de interfaces que no usan.

# Malo: una interfaz gorda
class Worker(ABC):
    @abstractmethod
    def work(self):
        pass
    @abstractmethod
    def eat(self):  # los robots no comen
        pass
    @abstractmethod
    def sleep(self):  # los robots no duermen
        pass

# Bueno: dividir en interfaces enfocadas
class Workable(ABC):
    @abstractmethod
    def work(self):
        pass

class Feedable(ABC):
    @abstractmethod
    def eat(self):
        pass

class HumanWorker(Workable, Feedable):
    def work(self): ...
    def eat(self): ...

class RobotWorker(Workable):
    def work(self): ...
    # no necesita implementar eat() ni sleep()

Por qué importa: Las interfaces gordas crean acoplamiento innecesario. Cuando un cliente depende de métodos que no usa, cambios en esos métodos pueden forzar recompilación o retesteo innecesario.

D — Principio de Inversión de Dependencias (DIP)

Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.

# Malo: módulo de alto nivel depende de módulo de bajo nivel concreto
class EmailService:
    def send(self, to, subject, body):
        ...  # lógica SMTP

class NotificationManager:  # alto nivel
    def __init__(self):
        self.email = EmailService()  # dependencia hardcodeada

    def notify_user(self, user):
        self.email.send(user.email, "Hola", "...")

# Bueno: depender de abstracciones
class NotificationChannel(ABC):
    @abstractmethod
    def send(self, to, subject, body):
        pass

class EmailService(NotificationChannel):
    def send(self, to, subject, body):
        ...

class SMSService(NotificationChannel):
    def send(self, to, subject, body):
        ...

class NotificationManager:
    def __init__(self, channel: NotificationChannel):
        self.channel = channel

    def notify_user(self, user):
        self.channel.send(user.email, "Hola", "...")

# Fácil cambiar implementaciones sin tocar NotificationManager
email_notifier = NotificationManager(EmailService())
sms_notifier = NotificationManager(SMSService())

Por qué importa: Depender de abstracciones hace el sistema flexible. Puedes intercambiar implementaciones (para testing, diferentes entornos, o nuevos requerimientos) sin tocar la lógica de negocio de alto nivel.

Aplicando SOLID Juntos

Los principios SOLID se refuerzan mutuamente:

PrincipioSoporta
SRPFacilita OCP (clases más pequeñas = más fáciles de extender)
OCPHabilita LSP (extensión vía herencia/sustitución)
LSPHabilita polimorfismo usado por DIP
ISPReduce la superficie de dependencias para DIP
DIPHabilita OCP permitiendo inyección de comportamiento

Errores Comunes

  • Crear una clase por método para forzar SRP — no toda función necesita su propia clase
  • Usar OCP como excusa para abstracción prematura — YAGNI aún aplica
  • Aplicar mal LSP a value objects que no están destinados a ser sustituibles
  • Dividir interfaces tan finamente que el sistema se fragmenta
  • Inyectar dependencias en todas partes incluyendo utilidades triviales y estables

Preguntas Frecuentes

¿Debería aplicar todos los principios SOLID a cada clase?

No. Son guías, no leyes. Aplícalos donde reduzcan complejidad y acoplamiento. Scripts pequeños y operaciones CRUD a menudo no necesitan tratamiento SOLID completo.

¿Los principios SOLID aplican solo a POO?

Los conceptos se traducen bien a otros paradigmas. La programación funcional logra DIP mediante funciones de orden superior, y SRP aplica a módulos y funciones en cualquier paradigma.

¿Cómo convenzo a mi equipo de refactorizar hacia SOLID?

No refactorices por los principios en sí. Espera hasta que se necesite un cambio, luego usa los principios para guiar un diseño más limpio. Muestra comparaciones antes/después en PRs.