Principios SOLID

La base para un codigo robusto y mantenible

¿Que son los Principios SOLID? Son cinco principios de diseno orientado a objetos que, cuando se aplican juntos, hacen que el codigo sea mas mantenible, flexible y facil de entender. El acronimo SOLID fue acunado por Robert C. Martin (Uncle Bob).

SOLID: El Cimiento de un Buen Codigo

Imagina que estas construyendo un edificio. Si los cimientos no son solidos, todo se vendra abajo eventualmente. Lo mismo ocurre con el software.

Los principios SOLID son como las reglas de la buena construccion, pero para tu codigo.

S

Single Responsibility Principle

Principio de Responsabilidad Unica

"Una clase debe tener una, y solo una, razon para cambiar."

Ejemplo en la vida real:

Un cocinero en un restaurante se especializa en preparar platos, no en tomar ordenes, lavar platos y cobrar a los clientes.

Mal ejemplo:

class Usuario {
    // Datos del usuario
    private String nombre;
    private String email;
    
    // Guardar usuario (responsabilidad de persistencia)
    public void guardarEnBaseDeDatos() { ... }
    
    // Enviar email (responsabilidad de comunicacion)
    public void enviarEmailBienvenida() { ... }
    
    // Validar datos (responsabilidad de validacion)
    public boolean validarEmail() { ... }
}

Buen ejemplo:

class Usuario {
    private String nombre;
    private String email;
    // Solo getters y setters
}

class UsuarioRepository {
    public void guardar(Usuario usuario) { ... }
}

class EmailService {
    public void enviarBienvenida(Usuario usuario) { ... }
}

class ValidadorUsuario {
    public boolean validarEmail(String email) { ... }
}
O

Open/Closed Principle

Principio Abierto/Cerrado

"Las entidades de software deben estar abiertas para su extension, pero cerradas para su modificacion."

Ejemplo en la vida real:

Un control remoto universal que puede programarse para nuevos dispositivos sin tener que cambiar su diseno interno.

Mal ejemplo:

class CalculadoraImpuestos {
    public double calcularImpuesto(Producto producto) {
        if (producto.tipo == "ALIMENTO") {
            return producto.precio * 0.07;
        } else if (producto.tipo == "ELECTRONICO") {
            return producto.precio * 0.18;
        }
        // Si anadimos un nuevo tipo, tenemos que modificar esta clase
        return 0;
    }
}

Buen ejemplo:

interface CalculadorImpuesto {
    double calcularImpuesto(double precio);
}

class ImpuestoAlimentos implements CalculadorImpuesto {
    public double calcularImpuesto(double precio) {
        return precio * 0.07;
    }
}

class ImpuestoElectronicos implements CalculadorImpuesto {
    public double calcularImpuesto(double precio) {
        return precio * 0.18;
    }
}

// Para anadir un nuevo tipo, solo creamos una nueva clase
class ImpuestoRopa implements CalculadorImpuesto {
    public double calcularImpuesto(double precio) {
        return precio * 0.12;
    }
}
L

Liskov Substitution Principle

Principio de Sustitucion de Liskov

"Las clases derivadas deben poder sustituir a sus clases base sin alterar el comportamiento esperado."

Ejemplo en la vida real:

Si reemplazas a un chef con su aprendiz, los clientes deben seguir recibiendo comida de la misma calidad.

Mal ejemplo:

class Ave {
    public void volar() {
        System.out.println("Volando...");
    }
}

class Pinguino extends Ave {
    @Override
    public void volar() {
        // Los pinguinos no pueden volar, esto rompe el principio
        throw new UnsupportedOperationException("Los pinguinos no vuelan");
    }
}

Buen ejemplo:

interface Ave {
    void moverse();
}

class AveVoladora implements Ave {
    public void moverse() {
        System.out.println("Volando...");
    }
}

class Pinguino implements Ave {
    public void moverse() {
        System.out.println("Nadando y caminando...");
    }
}
I

Interface Segregation Principle

Principio de Segregacion de Interfaces

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

Ejemplo en la vida real:

Un smartphone moderno tiene muchas funciones, pero un usuario mayor quizas solo quiera hacer llamadas y enviar mensajes, no necesita todas las opciones avanzadas.

Mal ejemplo:

interface Trabajador {
    void trabajar();
    void comer();
    void dormir();
}

// Un robot puede trabajar pero no necesita comer ni dormir
class Robot implements Trabajador {
    public void trabajar() {
        // Implementacion
    }
    
    public void comer() {
        // Los robots no comen, metodo vacio o excepcion
    }
    
    public void dormir() {
        // Los robots no duermen, metodo vacio o excepcion
    }
}

Buen ejemplo:

interface Trabajable {
    void trabajar();
}

interface Comedor {
    void comer();
}

interface Durmiente {
    void dormir();
}

class Humano implements Trabajable, Comedor, Durmiente {
    // Implementa todos los metodos
}

class Robot implements Trabajable {
    // Solo implementa lo que necesita
    public void trabajar() {
        // Implementacion
    }
}
D

Dependency Inversion Principle

Principio de Inversion de Dependencias

"Los modulos de alto nivel no deben depender de modulos de bajo nivel. Ambos deben depender de abstracciones."

Ejemplo en la vida real:

Cuando enchufas un aparato, no te preocupas por como se genera la electricidad (carbon, nuclear, solar). El enchufe es una abstraccion que permite que todo funcione sin conocer los detalles.

Mal ejemplo:

class NotificadorEmail {
    public void enviar(String mensaje, String destinatario) {
        // Logica para enviar email
    }
}

class ServicioNotificacion {
    private NotificadorEmail notificador = new NotificadorEmail();
    
    public void notificarUsuario(Usuario usuario, String mensaje) {
        // Dependencia directa a una implementacion concreta
        notificador.enviar(mensaje, usuario.getEmail());
    }
}

Buen ejemplo:

interface Notificador {
    void enviar(String mensaje, String destinatario);
}

class NotificadorEmail implements Notificador {
    public void enviar(String mensaje, String destinatario) {
        // Logica para enviar email
    }
}

class NotificadorSMS implements Notificador {
    public void enviar(String mensaje, String destinatario) {
        // Logica para enviar SMS
    }
}

class ServicioNotificacion {
    private final Notificador notificador;
    
    // Inyeccion de dependencia
    public ServicioNotificacion(Notificador notificador) {
        this.notificador = notificador;
    }
    
    public void notificarUsuario(Usuario usuario, String mensaje) {
        notificador.enviar(mensaje, usuario.getContacto());
    }
}

Beneficios de Aplicar SOLID

Mantenibilidad

Codigo mas facil de mantener y actualizar con el tiempo.

Escalabilidad

Facilita el crecimiento de la aplicacion sin reescribir grandes partes.

Menos Bugs

Codigo mas predecible y con menos efectos secundarios inesperados.

Testabilidad

Facilita la escritura de pruebas unitarias efectivas.

Los principios SOLID no son reglas rigidas, sino guias para escribir mejor codigo. Recuerda:

  • Aplicalos con sentido comun - No fuerces los principios donde no son necesarios
  • Evolucion gradual - Mejora tu codigo paso a paso, no intentes hacerlo perfecto de inmediato
  • Balance - A veces, un poco de pragmatismo es mejor que el purismo teorico