
📜 Origen de los principios SOLID
Antes de meternos de lleno con cada principio y todo lo que es esto, vamos a ver de dónde salen y por qué se hicieron tan famosillos en la industria.
Los principios que hoy conocemos como SOLID no aparecieron de la nada. Fueron recopilados y viralizados por Robert C. Martin, más conocido como Uncle Bob, 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 a 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 en el 2004 que se dio cuenta de que las iniciales de los cinco principios formaban la palabra SOLID y se lo comentó a Uncle Bob. A partir de ahí el nombre pegó muy duro y se quedó.
¿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 empezaron 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 complicado de meter mano.
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 SOLID es 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 por estos principios casi seguro. Es una base sólida para 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 en detalle.
🧩 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ó que quiere decir que si tocas algo aquí la lias parda allí. ¿Te suena? A todos nos ha pasado.
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: 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 lleva el nombre de Barbara Liskov, una científica de la computación del MIT que lo formuló en 1987. Fue una de las primeras mujeres en obtener un doctorado en Ciencias de la Computación en Estados Unidos y ganó el Premio Turing en 2008 (el “Nobel de la informática”). Así que sí, este principio tiene solera.
¿Qué significa en cristiano?
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 famoso 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 realmente 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 genera:
- Métodos vacíos o que lanzan excepciones tipo “no soportado”
- 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()? Pos claro que no.
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. Eso no mola nada nene.
Ejemplo
// MAL: la clase de alto nivel depende de una implementación concreta
class ServicioNotificaciones {
private $email = new EnviarPorEmail(); // Acoplamiento directo
public function notificar(string $mensaje): void {
$this->email->enviar($mensaje);
}
}
// BIEN: depende de una abstracción
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 ni le importa 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 listo.
Post dedicado: 👉 Dependency Inversion Principle en detalle
⚠️ ¿Cuándo NO aplicar SOLID?
Esto es muy importante y mucha gente se lo salta. SOLID no es una religión. No hay que aplicar estos principios siempre a toda costa, porque el resultado puede ser peor que el problema original.
Hay situaciones donde aplicar SOLID a rajatabla 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: Los principios SOLID son herramientas, no leyes. Úsalos cuando aporten valor real al proyecto, no por cumplir un checklist.
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 popularizaron con el movimiento Agile y hoy son prácticamente un estándar en la industria.
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?