Principios SOLID explicados con ejemplos claros

Principios SOLID: Qué son, ejemplos y cómo los he aplicado en mis proyectos

Los principios SOLID son una guía fundamental para escribir software más limpio, escalable y fácil de mantener. Aunque el término existe desde principios del 2000, todavía hay muchos equipos que no los aplican de forma consciente, y eso provoca diseños rígidos, difíciles de probar o de reutilizar.

En SernaLabs los he ido incorporando en mis proyectos recientes, y aquí te cuento —de forma práctica y aplicada— qué significa cada principio y cómo me han ayudado a mejorar la calidad de mi código.


¿Qué significan los principios SOLID?

  • Single Responsibility (Responsabilidad Única)
  • Open/Closed (Abierto/Cerrado)
  • Liskov Substitution (Sustitución de Liskov)
  • Interface Segregation (Segregación de Interfaces)
  • Dependency Inversion (Inversión de Dependencias)

A continuación, te explico cada uno con ejemplos claros y aplicados.


Single Responsibility Principle (SRP)

Este principio indica que una clase, módulo o microservicio debe tener una sola razón para cambiar. Una sola responsabilidad.

Ejemplo sencillo:

Si tienes una clase Libro, debería atender únicamente lo relacionado con préstamos o administración del libro: Prestar, Devolver, Retirar. Nada de agregar métodos como VerHistorialUsuarios o EnviarAvisos, porque eso le pertenece a otras responsabilidades.

Cuando respetas este principio:

  • El código es más fácil de probar.
  • Las clases se vuelven más predecibles.
  • Los cambios no rompen funcionalidades no relacionadas.

public class Libro {
    public string Titulo { get; set; }
    public bool EnPrestamo { get; private set; }

    public void Prestar() {
        if (!EnPrestamo) EnPrestamo = true;
    }

    public void Devolver() {
        if (EnPrestamo) EnPrestamo = false;
    }
}

// Otra responsabilidad → otra clase
public class NotificadorUsuario {
    public void EnviarAviso(string mensaje) {
        // Lógica para enviar correos o notificaciones
    }
}

Open-Closed Principle (OCP)

El código debe estar cerrado a modificaciones pero abierto a extensiones. Es decir, no alteras código que ya funciona; lo amplías creando nuevas clases o heredando comportamientos.

Este ejemplo lo utilicé en un proyecto de matemáticas computacionales, calculando distancias y productos vectoriales:


public abstract class Punto {
    public float X { get; set; }
    public float Y { get; set; }
    public float Z { get; set; }
}

public abstract class MathVector {
    public Punto A { get; set; }
    public Punto B { get; set; }

    public float ProductoPunto => (A.X * B.X) + (A.Y * B.Y) + (A.Z * B.Z);
    public float ProductoCruz => (A.Y * B.Z) + (A.Z * B.X) + (A.X * B.Y)
                               - (A.Y * B.X) - (A.Z * B.Y) - (A.X * B.Z);
}

public class Punto3D : Punto {
    public Punto3D(float x, float y, float z) {
        X = x;
        Y = y;
        Z = z;
    }
}

public class Vector3D : MathVector {
    public Vector3D(Punto origen, Punto destino) {
        A = origen;
        B = destino;
    }

    public double ObtenerDistancia() {
        float X = A.X - B.X;
        float Y = A.Y - B.Y;
        float Z = A.Z - B.Z;

        return Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
    }
}

Con este diseño, puedo extender comportamientos sin modificar las clases base.


Liskov Substitution Principle (LSP)

Una clase hija debe poder sustituir a su clase base sin alterar el correcto funcionamiento del sistema.

En otras palabras:

Si heredas, compórtate como tu clase padre.

En proyectos reales, LSP te obliga a diseñar herencias coherentes y evita “hijitos rebeldes” que rompen lógica al reemplazarlos por la clase base.


public abstract class Figura {
    public abstract double Area();
}

public class Cuadrado : Figura {
    public double Lado { get; set; }

    public override double Area() => Lado * Lado;
}

public class Circulo : Figura {
    public double Radio { get; set; }

    public override double Area() => Math.PI * Radio * Radio;
}

public class CalculadoraArea {
    public double Calcular(Figura figura) {
        return figura.Area(); // No importa si es círculo o cuadrado, funciona igual
    }
}

Interface Segregation Principle (ISP)

Este principio promueve interfaces pequeñas y específicas en vez de interfaces enormes y genéricas.

¿Por qué es tan importante?

  • Evitas obligar a una clase a implementar métodos que no necesita.
  • Fomentas diseños más limpios y modulares.
  • Ayuda a que cada interfaz represente una responsabilidad concreta.

En la práctica, es complementario a SRP.


public interface IPrestable {
    void Prestar();
    void Devolver();
}

public interface ICatalogable {
    string ObtenerDescripcion();
}

public class Revista : IPrestable, ICatalogable {
    public string Nombre { get; set; }

    public void Prestar() { /* ... */ }
    public void Devolver() { /* ... */ }

    public string ObtenerDescripcion() {
        return $"Revista: {Nombre}";
    }
}

public class PosterDecorativo : ICatalogable {
    public string Titulo { get; set; }

    public string ObtenerDescripcion() {
        return $"Póster: {Titulo}";
    }
}


Dependency Inversion Principle (DIP)

Este principio ayuda a desacoplar el código. En lugar de depender de implementaciones concretas, dependes de abstracciones como interfaces.

Ejemplo típico:

  • No instancias directamente new SqlRepository().
  • Recibes IRepository en el constructor.

¿El resultado?

  • Clases más fáciles de probar.
  • Mayor flexibilidad para sustituir implementaciones.
  • Menos dependencia rígida entre módulos.

public interface IRepositorioUsuarios {
    void Guardar(string nombre);
}

public class SqlRepositorioUsuarios : IRepositorioUsuarios {
    public void Guardar(string nombre) {
        // Guardar en SQL
    }
}

public class ServicioRegistro {
    private readonly IRepositorioUsuarios _repositorio;

    public ServicioRegistro(IRepositorioUsuarios repositorio) {
        _repositorio = repositorio;
    }

    public void Registrar(string nombre) {
        _repositorio.Guardar(nombre);
    }
}


Conclusión

Adoptar los principios SOLID puede parecer complicado al inicio, pero una vez que los aplicas, tus proyectos se vuelven más flexibles, ordenados y fáciles de mantener. Incluso si al principio implica escribir más código, la ganancia está en la claridad, la escalabilidad y la capacidad de prueba.

Mi recomendación: empieza con uno o dos principios, aplícalos en tus proyectos personales, y poco a poco verás cómo tu forma de diseñar software evoluciona.


Si quieres conocer un poco más sobre mí y mi trabajo con tecnología e IA, puedes visitar mi página de Quién soy y qué hago. También encontrarás detalles sobre los servicios que ofrezco en Por qué elegirme / Qué ofrezco. Y si te interesa la integración de IA con salud y datos médicos, echa un vistazo a API Médica.

Comentarios

Entradas más populares de este blog

API Multi-Tenant para Expedientes Médicos – Arquitectura, Seguridad y Estado Actual del Proyecto

Patrones de Diseño en C# — Colección práctica y guía para desarrolladores

Paradigmas de programación: distintas formas de pensar el código