Principios SOLID
¿Qué son? ¿por qué has oído hablar tanto de ellos? ¿para qué sirven? ¿los tengo que usar?
En este POST vamos a ver un poquito sobre los Principios SOLID y el porqué es interesante e importante interiorizarlos para poder escribir un código de mejor calidad. Luego vamos a ver en un post individual cada principio con ejemplos de uso, para explicar bien bien cada uno de ellos.
EA, amo al lío.

📜 Origen de los principios SOLID ¿de dónde salen?
Antes de meternos con el percal de ver cada principio, vamos a ver de dónde salen y cuando y por qué se hicieron tan famosillos en la industria del software.
Los principios SOLID que hoy conocemos no aparecieron de la nada. Fueron recopilados y viralizados por Robert C. Martin, más conocido en las redes como Uncle Bob. El Tío Bob es un ingeniero de software estadounidense que lleva décadas predicando las buenas prácticas en programación.
El bueno del tío Bob comenzó a escribir sobre estos principios entre finales de los años 90 y principios de los 2000 en artículos y en su libro “Agile Software Development, Principles, Patterns, and Practices” publicado en 2002. Pero ojito que el acrónimo SOLID como tal no lo inventó él. Fue Michael Feathers, a principios de los 2000, quien se dio cuenta de que reordenando las iniciales de los cinco principios salía la palabra SOLID y se lo comentó a Uncle Bob. A partir de ahí el nombre pegó muy duro y se quedó hasta nuestros días.
¿Por qué se hicieron tan conocidos?
La popularidad de SOLID se vino arriba con el auge del movimiento Agile y las metodologías ágiles a partir de los 2000. Cuando los equipos comenzaron a trabajar con ciclos cortos, entregas frecuentes y código que cambiaba constantemente, se dieron cuenta de que sin buenas prácticas de diseño el código se convertía rápidamente en un monstruo, al que meter mano era bastante complicado.
SOLID se convirtió en la respuesta a preguntas que todos los desarrolladores se hacían, y que bueno, aún se siguen haciendo XD
- ¿Por qué cada vez que toco una cosa se rompen otras tres?
- ¿Por qué añadir una funcionalidad nueva es tan complicado?
- ¿Por qué nadie entiende este código?
Hoy en día los principios SOLID son prácticamente un estándar en la industria. Si vas a una entrevista de trabajo para un puesto de backend (o de cualquier cosa relacionada con programación orientada a objetos), te van a preguntar si estás familiarizado con estos principios casi seguro. Es una base sólida para comenzar a escribir buen software.
🏛️ ¿Qué son los principios SOLID?
Los principios SOLID son un conjunto de cinco buenas prácticas para escribir código que sea fácil de entender, mantener y extender. Están pensados principalmente para la programación orientada a objetos (POO), aunque sus ideas se pueden aplicar en cualquier paradigma.
En el entorno profesional del software, añadir funcionalidades nuevas puede ser un auténtico drama si el código no está bien planteado desde un inicio. Los principios SOLID sirven como guía para intentar solventar este problema.
SOLID es un acrónimo donde cada letra representa un principio:
Single Responsibility
Open/Closed
Liskov Substitution
Interface Segregation
Dependency Inversion
Vamos a ver cada uno de cerca.
🧩 S : Single Responsibility Principle (SRP)
Una clase solo debe tener una razón para cambiar, es decir, solo debe tener una responsabilidad.
El SRP (Principio de Responsabilidad Única) dice que cada parte del código, por ejemplo una clase, debe encargarse de una sola cosa. Si una clase tiene más de una razón para cambiar, es que está haciendo demasiado.
¿Por qué es importante?
Cuando una clase tiene múltiples responsabilidades cualquier cambio en una de ellas puede afectar a las demás. Esto crea un efecto dominó en el que si tocas algo aquí, la lias parda allí.
Ejemplo
Imagina una clase Factura que se encarga de:
- Calcular el total de la factura
- Guardarla en base de datos
- Generar un PDF para imprimir
Esa clase tiene tres razones para cambiar: si cambia la lógica de cálculo, si cambia la base de datos o si cambia el formato del PDF. Eso incumple el SRP.
La solución es separar cada responsabilidad en su propia clase:
CalculadoraFactura: calcula importesRepositorioFactura: guarda en base de datosGeneradorPDFFactura: genera el PDF
Así cada clase tiene una sola razón para existir y si algo cambia, solo tocas la clase correspondiente.
Si quieres profundizar más en este principio con ejemplos de código, te dejo el post dedicado: 👉 Single Responsibility Principle en detalle
🔓 O : Open/Closed Principle (OCP)
Las entidades de software deben estar abiertas a la extensión pero cerradas a la modificación.
Este principio fue originalmente de Bertrand Meyer en 1988, pero el bueno de Bob lo adaptó al contexto de la POO moderna. Debes poder añadir funcionalidades nuevas sin tener que modificar el código que ya funciona.
¿Cómo funciona esto?
- Abierto a extensión: Puedes agregar nuevas funcionalidades derivando clases, implementando interfaces o usando composición.
- Cerrado a modificación: No tocas el código que ya está en producción y funcionando. Así te aseguras de que lo que ya funciona no se rompe.
Ejemplo
Imagina que tienes una clase CalculadoraDescuentos que aplica descuentos según el tipo de cliente. Si cada vez que aparece un tipo de cliente nuevo tienes que abrir esa clase y meter un if más, estás incumpliendo este principio.
La solución es crear una interfaz Descuento y que cada tipo de descuento sea una clase que la implemente. Cuando aparezca un tipo de cliente nuevo, solo creas una clase nueva sin tocar las existentes.
interface Descuento {
public function calcular(float $precio): float;
}
class DescuentoEstudiante implements Descuento {
public function calcular(float $precio): float {
return $precio * 0.80; // 20% descuento
}
}
class DescuentoEmpleado implements Descuento {
public function calcular(float $precio): float {
return $precio * 0.70; // 30% descuento
}
}
// Si mañana aparece un DescuentoVIP, solo creas una clase nueva.
// No tocas NADA de lo que ya funciona.
Post dedicado con más detalle: 👉 Open/Closed Principle en detalle
🔄 L : Liskov Substitution Principle (LSP)
Si S es un subtipo de T, entonces los objetos de tipo T pueden ser reemplazados por objetos de tipo S sin alterar el comportamiento correcto del programa.
Este principio tiene el nombre de Barbara Liskov, una científica de la computación del MIT. Como dato, Liskov fue una de las primeras mujeres en obtener un doctorado Ciencias de la Computación en Estados Unidos.
¿Qué significa este principio?
Que cualquier instancia de una clase hija debe poder usarse en lugar de una instancia de su clase padre sin que nada se rompa. Si tu código funciona con la clase Ave, debe funcionar igual si le pasas un Pato, un Águila o un Pingüino.
El ejemplo clásico que siempre sale
El ejemplo más conocido para explicar la violación de Liskov es el del Rectángulo y el Cuadrado. Matemáticamente, un cuadrado es un rectángulo (todos los cuadrados son rectángulos). Pero en programación, si Cuadrado hereda de Rectángulo y sobreescribes los setters para que ancho y alto siempre sean iguales, rompes el contrato de Rectángulo, porque quien use un Rectángulo espera poder cambiar ancho y alto de forma independiente.
¿Cómo detectar que estás violando Liskov?
Si en tu código tienes cosas como estas, desconfía:
if (objeto instanceof TipoConcreto)para decidir qué hacer- Una subclase que lanza excepciones en métodos que la clase padre no lanzaba
- Una subclase que ignora o deja vacíos métodos heredados
Post dedicado: 👉 Liskov Substitution Principle en detalle
✂️ I : Interface Segregation Principle (ISP)
Los clientes no deben verse obligados a depender de interfaces que no utilizan.
El Principio de Segregación de Interfaces nos dice que es mejor tener muchas interfaces pequeñas y específicas en lugar de una interfaz gigante que lo haga todo. Así cada clase solo implementa los métodos que necesita.
¿Por qué importa?
Cuando una interfaz es demasiado grande y una clase la implementa, esa clase se ve obligada a tener métodos que no necesita, esto quiere decir que tendremos:
- Métodos vacíos o que lanzan excepciones tipo not implemented.
- Acoplamiento innecesario: un cambio en un método que no usas te obliga a recompilar o modificar tu clase.
- Confusión: otros desarrolladores no saben qué métodos son realmente relevantes.
Ejemplo del restaurante
Imagina que creas una interfaz EmpleadoRestaurante con estos métodos:
tomarPedidos()cobrar()cocinar()entregarPedidosADomicilio()limpiarMesas()
¿Un cocinero debería implementar entregarPedidosADomicilio()? ¿Un repartidor debería implementar cocinar()? NOPE!
La solución es separar esa interfaz gorda en varias más pequeñas:
Cocinero→cocinar()Cajero→cobrar(),tomarPedidos()Repartidor→entregarPedidosADomicilio()Limpiador→limpiarMesas()
Cada empleado implementa solo las interfaces de lo que realmente hace.
Post dedicado: 👉 Interface Segregation Principle en detalle
🏗️ D : Dependency Inversion Principle (DIP)
Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.
Este principio es para muchos el más potente de los cinco. La idea es que en lugar de que tu código dependa directamente de implementaciones concretas (una clase específica), debería depender de abstracciones (interfaces o clases abstractas).
¿Qué es “alto nivel” y “bajo nivel”?
- Alto nivel: la lógica de negocio de tu aplicación, lo que decide qué se hace.
- Bajo nivel: los detalles de implementación, lo que decide cómo se hace (enviar un email, guardar en base de datos, llamar a una API…).
Si tu clase de alto nivel instancia directamente una clase de bajo nivel, estás creando un acoplamiento fuerte. Si mañana cambias la base de datos de MySQL a PostgreSQL, tienes que modificar la clase de negocio, y eso no mola.
Ejemplo
// MAL: la clase de alto nivel crea por dentro su propia dependencia
class ServicioNotificaciones {
private EnviarPorEmail $email;
public function __construct() {
$this->email = new EnviarPorEmail(); // Acoplamiento directo a una clase concreta
}
public function notificar(string $mensaje): void {
$this->email->enviar($mensaje);
}
}
// BIEN: depende de una abstracción y se la inyectan desde fuera
interface CanalNotificacion {
public function enviar(string $mensaje): void;
}
class ServicioNotificaciones {
public function __construct(
private CanalNotificacion $canal // Inyecta la abstracción
) {}
public function notificar(string $mensaje): void {
$this->canal->enviar($mensaje);
}
}
Ahora puedes pasar EnviarPorEmail, EnviarPorSMS, EnviarPorTelegram o lo que sea. El servicio de notificaciones no sabe cómo se envía el mensaje, solo que se envía. Si mañana quieres añadir notificaciones por paloma mensajera, solo creas una clase nueva que implemente CanalNotificacion y ya.
Post dedicado: 👉 Dependency Inversion Principle en detalle
⚠️ ¿Cuándo NO aplicar SOLID?
SOLID no es una religión. No hay que aplicar estos principios siempre con calzador, porque el resultado puede ser peor que el problema original.
Hay situaciones donde aplicar SOLID es contraproducente:
- Scripts pequeños o de un solo uso: Si estás haciendo un script para migrar datos que vas a ejecutar una vez y tirar, no necesitas interfaces ni abstracciones. Haz lo que tengas que hacer y ya.
- Prototipos y MVPs: Cuando estás validando una idea, la velocidad importa más que la arquitectura perfecta. Ya refactorizarás cuando sepas que el proyecto va en serio.
- Sobre-ingeniería: Crear 15 interfaces y 20 clases para algo que se resuelve con una función de 10 líneas es pegarse un tiro en el pie. El código más fácil de mantener es el que no existe.
- Equipos pequeños con contexto compartido: Si sois dos personas trabajando en un proyecto pequeño y os conocéis el código de memoria, la flexibilidad extrema de SOLID puede añadir complejidad innecesaria.
Recuerda que los principios SOLID son herramientas, no leyes. Úsalos cuando aporten valor real al proyecto.
Conclusión
Los principios SOLID llevan más de dos décadas ayudando a los desarrolladores a escribir mejor código. Nacieron de la experiencia de gente como Robert C. Martin y Barbara Liskov, se hicieron conocidos con el movimiento Agile y hoy son un estándar en la industria del software.
No son la solución a todos los problemas, pero si los entiendes y sabes cuándo aplicarlos, tu código será más limpio, más mantenible y más profesional.
EA! nos leemos! 🍻
Pon a prueba lo aprendido
1. ¿Quién popularizó los principios SOLID?
2. ¿Qué dice el Principio de Responsabilidad Única (SRP)?
3. ¿Qué significa que una clase esté 'abierta a extensión pero cerrada a modificación'?
4. ¿Quién formuló el Principio de Sustitución de Liskov y en qué año?
5. ¿Qué principio SOLID se viola si una clase implementa métodos que no utiliza?
6. Según el Principio de Inversión de Dependencias, ¿de qué deben depender los módulos de alto nivel?
7. ¿Quién inventó el acrónimo SOLID?