Principios SOLID en Java: de la teoría a la práctica avanzada
Los principios SOLID son la base del diseño orientado a objetos que ayuda a los desarrolladores a crear código resistente a cambios, fácil de probar y escalable. Para un desarrollador Java, comprender estos principios a nivel avanzado no es solo conocer las definiciones, sino saber identificar sus violaciones en código legacy y aplicar patrones para refactorizar. En este artículo analizaremos cada principio con énfasis en situaciones complejas pero típicas.
1. Principio de Responsabilidad Única (SRP): El límite entre responsabilidad y cohesión
Teoría: Una clase debe tener solo una razón para cambiar. En la práctica, esto significa que la clase debe ser responsable de una sola parte de la funcionalidad del programa.
Práctica avanzada: A menudo, la violación del SRP no se manifiesta en una mezcla explícita de lógica, sino en una responsabilidad "difusa". Consideremos un ejemplo de clase para trabajar con informes:
// Violación de SRP: la clase se ocupa de datos, formato y envíopublic class ReportService { public void generateReport(List<Data> data) { // Lógica de cálculos Map<String, Integer> aggregated = aggregate(data); // Formateo (HTML) String html = formatToHtml(aggregated); // Envío por email sendEmail(html, "admin@example.com"); }
private Map<String, Integer> aggregate(List<Data> data) { // ... return new HashMap<>(); }
private String formatToHtml(Map<String, Integer> data) { // ... return "<html>...</html>"; }
private void sendEmail(String content, String to) { // ... }}Problema: si cambia el formato del informe (se necesita PDF en lugar de HTML), habrá que modificar esta clase. Si cambia el método de envío (Telegram en lugar de email), también. Solución: dividir en tres clases:
public class ReportAggregator { public Map<String, Integer> aggregate(List<Data> data) { ... }}
public class HtmlReportFormatter { public String format(Map<String, Integer> data) { ... }}
public class EmailSender { public void send(String content, String to) { ... }}
// Composición en un serviciopublic class ReportGenerationFacade { private ReportAggregator aggregator; private HtmlReportFormatter formatter; private EmailSender sender;
public void generateAndSend(List<Data> data) { var aggregated = aggregator.aggregate(data); var html = formatter.format(aggregated); sender.send(html, "admin@example.com"); }}2. Principio Abierto-Cerrado (OCP): El arte de la extensión mediante abstracciones
Teoría: Las clases deben estar abiertas para extensión, pero cerradas para modificación.
Práctica avanzada: Una violación típica son las cadenas de if-else o switch para diferentes tipos de objetos. La forma avanzada es usar el patrón estrategia o el método plantilla con polimorfismo.
// Violación de OCP: al agregar un nuevo tipo de pago, hay que modificar la clasepublic class PaymentProcessor { public void processPayment(String type, double amount) { if ("CREDIT_CARD".equals(type)) { // lógica para tarjeta de crédito } else if ("PAYPAL".equals(type)) { // lógica para PayPal } else if ("BITCOIN".equals(type)) { // lógica para Bitcoin } }}Corrección mediante interfaz y fábrica:
public interface PaymentMethod { void pay(double amount);}
public class CreditCardPayment implements PaymentMethod { public void pay(double amount) { ... }}
public class PayPalPayment implements PaymentMethod