Princípios SOLID em Java: da teoria ao código limpo
Todo desenvolvedor Java, mais cedo ou mais tarde, enfrenta o problema do código "sujo": classes que fazem tudo ao mesmo tempo, dependências rígidas e impossibilidade de extensão sem quebrar a lógica existente. É aqui que os princípios SOLID entram em ação — cinco regras fundamentais de design orientado a objetos, formuladas por Robert Martin (Uncle Bob). Neste artigo, vamos analisar cada princípio na prática com exemplos em Java, para que seu código se torne flexível, testável e fácil de manter.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
Essência: "Uma classe deve ter apenas um motivo para mudar". Em outras palavras, a classe deve ser responsável por uma tarefa claramente definida.
Violação do SRP
Considere a classe OrderService, que simultaneamente calcula o preço, salva no banco de dados e envia e-mail:
public class OrderService { public void processOrder(Order order) { // 1. Cálculo do valor double total = order.getItems().stream() .mapToDouble(Item::getPrice) .sum();
// 2. Salvamento no BD Database.save(order);
// 3. Envio de notificação EmailService.send("Pedido realizado no valor de: " + total); }}Problema: Alterar a lógica de envio de e-mails ou a forma de salvamento forçará a modificação de OrderService. Isso viola o SRP.
Refatoração segundo o SRP
Dividimos as responsabilidades em três classes separadas:
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 no valor 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 classe tem um único motivo para mudar. O código se torna modular e testável.
2. Open-Closed Principle (OCP) — Princípio Aberto/Fechado
Essência: "Entidades de software devem estar abertas para extensão, mas fechadas para modificação". Nova funcionalidade é adicionada através de extensão, e não alterando o código existente.
Violação do OCP
Imagine um sistema de descontos onde cada novo tipo de desconto exige alteração no 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; } // Adicionar novo tipo → alterar a classe return price; }}Solução através de polimorfismo
Usamos uma interface e diferentes implementações:
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; }}
// Novo tipo — apenas uma nova classe