✨ Introducción a SOLID
SOLID es un conjunto de 5 principios de diseño de software orientado a objetos que ayudan a escribir código más limpio, mantenible y fácil de modificar.
No es una tecnología, ni una librería, ni algo propio de Java. Es simplemente una forma de pensar cómo diseñar clases.
Los principios fueron propuestos por Robert C. Martin (Uncle Bob), un ingeniero de software muy famoso.
- Los introdujo a principios de los años 2000.
- Estaban relacionados con ideas anteriores de programación orientada a objetos.
- Más tarde alguien agrupó los 5 principios y formó el acrónimo SOLID.
El objetivo era responder a un problema clásico:
¿Por qué muchos programas orientados a objetos acaban siendo un caos?
Porque las clases:
- hacen demasiadas cosas
- dependen demasiado unas de otras
- son difíciles de cambiar
SOLID intenta evitar eso.
Ayudan a que el código sea:
- 🧱 Más mantenible
- 🔄 Más extensible
- 🧠 Más fácil de entender
🔹¿Qué significa SOLID?
SOLID es un acrónimo con 5 principios:
| Principio | Idea clave |
|---|---|
| 🟢 S – Single Responsibility | Una clase debe tener una única responsabilidad |
| 🔵 O – Open/Closed | El código debe permitir ampliarse sin modificarse |
| 🟡 L – Liskov Substitution | Los hijos deben poder sustituir al padre |
| 🟣 I – Interface Segregation | Mejor interfaces pequeñas y específicas que una enorme |
| 🔴 D – Dependency Inversion | Depender de abstracciones, no de implementaciones |
S — Single Responsibility Principle
👉 Una clase debe tener una sola responsabilidad.
Mal ejemplo
class Factura {
void calcularTotal() { }
void guardarEnBD() { }
void enviarEmail() { }
}
Esta clase hace tres cosas distintas:
- calcular el total
- guardar en la base de datos
- enviar un email
Si cambia el sistema de email, habría que modificar esta clase.
Mejor diseño
class Factura {
void calcularTotal() {}
}
class FacturaRepository {
void guardar(Factura f) {}
}
class EmailService {
void enviarFactura(Factura f) {}
}
Ahora cada clase tiene una única responsabilidad.
O — Open/Closed Principle
👉 El software debe estar abierto a extensión pero cerrado a modificación.
Mal ejemplo
double calcularPrecio(TipoCliente tipo) {
if(tipo == NORMAL) return 10;
if(tipo == VIP) return 8;
}
Si aparece otro tipo de cliente hay que modificar el código existente.
Mejor diseño
interface Cliente {
double calcularPrecio();
}
class ClienteNormal implements Cliente {
public double calcularPrecio() { return 10; }
}
class ClienteVIP implements Cliente {
public double calcularPrecio() { return 8; }
}
Ahora se pueden añadir nuevos tipos sin modificar código existente.
L — Liskov Substitution Principle
👉 Una subclase debe poder sustituir a su clase padre sin romper el programa.
Ejemplo problemático
class Ave {
void volar() {}
}
class Pinguino extends Ave {
void volar() {
throw new UnsupportedOperationException();
}
}
Un pingüino no puede volar, por lo que esta herencia está mal diseñada.
Mejor diseño
Ave no debería tener volar() si no, estás diciendo que todas las aves vuelan.
volar debe ir en una clase o interfaz más específica.
Se arregla quitando volar() de Ave y poniendo esa capacidad solo en las clases que realmente vuelan.
class Ave {
void ponerHuevos() {
System.out.println("El ave pone huevos");
}
}
interface Volador {
void volar();
}
class Aguila extends Ave implements Volador {
public void volar() {
System.out.println("El águila vuela");
}
}
class Pinguino extends Ave {
void nadar() {
System.out.println("El pingüino nada");
}
}
I — Interface Segregation Principle
👉 Es mejor tener muchas interfaces pequeñas que una muy grande.
Mal diseño
interface Animal {
void volar();
void nadar();
}
Un perro tendría que implementar volar() aunque no vuele.
Mejor diseño
interface Volador {
void volar();
}
interface Nadador {
void nadar();
}
Cada clase implementa solo lo que necesita.
D — Dependency Inversion Principle
👉 Las clases deben depender de interfaces, no de implementaciones concretas.
Mal ejemplo
class ServicioPedido {
MySQLDatabase db = new MySQLDatabase();
}
La clase queda fuertemente acoplada a MySQL.
Mejor diseño
interface Database {
void guardar();
}
class MySQLDatabase implements Database {}
class ServicioPedido {
Database db;
ServicioPedido(Database db) {
this.db = db;
}
}
Ahora el sistema puede cambiar de base de datos fácilmente.
🎯 Ejemplo práctico aplicando SOLID
❌ Diseño incorrecto
class PagoService {
public double procesarPago(String tipo, double importe) {
if (tipo.equals("TARJETA")) {
return importe * 1.02;
}
if (tipo.equals("BIZUM")) {
return importe + 1;
}
return importe;
}
}
Problemas:
- ❌ Viola Open/Closed
- ❌ Cada nuevo tipo requiere modificar la clase
✅ Diseño correcto con polimorfismo
interface MetodoPago {
double calcularTotal(double importe);
}
class PagoTarjeta implements MetodoPago {
public double calcularTotal(double importe) {
return importe * 1.02;
}
}
class PagoBizum implements MetodoPago {
public double calcularTotal(double importe) {
return importe + 1;
}
}
Uso:
public class Main {
public static void main(String[] args) {
MetodoPago pago = new PagoTarjeta();
System.out.println(pago.calcularTotal(100));
}
}
📌 Ahora el sistema está abierto a extensión pero cerrado a modificación.
🏁 Conclusión
Los enums con comportamiento y los principios SOLID ayudan a escribir código:
- 🎯 Más limpio
- 🧠 Más claro
- 🧱 Más fácil de ampliar
- 🔄 Más flexible
Dominar estas ideas es clave para pasar de POO básica a diseño profesional.