Principios SOLID en Java: de la teoría al código limpio
Cada desarrollador Java, tarde o temprano, se enfrenta al problema del código "sucio": clases que hacen todo a la vez, dependencias rígidas e imposibilidad de extensión sin romper la lógica existente. Aquí es donde los principios SOLID vienen al rescate: cinco reglas fundamentales de diseño orientado a objetos formuladas por Robert Martin (Tío Bob). En este artículo, analizaremos cada principio en la práctica con ejemplos en Java, para que tu código sea flexible, testeable y fácil de mantener.
1. Single Responsibility Principle (SRP) — Principio de Responsabilidad Única
Esencia: "Una clase debe tener una sola razón para cambiar". En otras palabras, una clase debe encargarse de una tarea claramente definida.
Violación de SRP
Consideremos la clase OrderService, que simultáneamente se encarga del cálculo del precio, el guardado en la base de datos y el envío de email:
public class OrderService { public void processOrder(Order order) { // 1. Cálculo del costo double total = order.getItems().stream() .mapToDouble(Item::getPrice) .sum();
// 2. Guardado en BD Database.save(order);
// 3. Envío de notificación EmailService.send("Pedido realizado por un total de: " + total); }}Problema: Cambiar la lógica de envío de correos o la forma de guardado obligará a modificar OrderService. Esto viola SRP.
Refactorización según SRP
Separamos las responsabilidades en tres clases distintas:
public class PriceCalculator { public double calculateTotal(Order order) { return order.getItems().stream() .mapToDouble(Item::getPrice) .sum(); }}
public class OrderRepository { public void save(Order order) { Database.save(order); }}
public class NotificationService { public void sendConfirmation(Order order) { EmailService.send("Pedido realizado por un total de: " + order.getTotal()); }}
// Código clientepublic class OrderProcessor { private PriceCalculator calculator; private OrderRepository repository; private NotificationService notifier;
public void processOrder(Order order) { double total = calculator.calculateTotal(order); order.setTotal(total); repository.save(order); notifier.sendConfirmation(order); }}Resultado: Cada clase tiene una sola razón para cambiar. El código se vuelve modular y testeable.
2. Open-Closed Principle (OCP) — Principio de Abierto/Cerrado
Esencia: "Las entidades de software deben estar abiertas para la extensión, pero cerradas para la modificación". La nueva funcionalidad se añade mediante extensión, no modificando el código existente.
Violación de OCP
Imaginemos un sistema de descuentos donde cada nuevo tipo de descuento requiere modificar el método applyDiscount:
public class DiscountService { public double applyDiscount(String type, double price) { if (type.equals("SUMMER")) { return price * 0.9; } else if (type.equals("VIP")) { return price * 0.8; } // Añadir un nuevo tipo → modificar la clase return price; }}Solución mediante polimorfismo
Usamos una interfaz y diferentes implementaciones:
public interface Discount { double apply(double price);}
public class SummerDiscount implements Discount { @Override public double apply(double price) { return price * 0.9; }}
public class VipDiscount implements Discount { @Override public double apply(double price) { return price * 0.8; }}
// Nuevo tipo — simplemente una nueva clas