Aplicar o Padrão SOLID em Java em 2026
Entendendo os Princípios SOLID
O acrônimo SOLID refere-se aos cinco principais princípios orientados a objetos introduzidos por Robert C. Martin: Single Responsibility Principle (SRP), Open/Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP) e Dependency Inversion Principle (DIP). Estes princípios guiam o desenvolvimento de software mais robusto.
Exemplos práticos em Java
1️⃣ Single Responsibility Principle (Responsabilidade Única)
Imagine uma classe UserService responsável pela criação, atualização e gerenciamento de usuários:
public class UserService {
public void saveUser(User user) { // Salva no banco
userRepository.save(user);
}
public User getUserById(Long id) { // Busca um usuário pelo ID
return userRepository.findById(id).orElse(null);
}
}
Este exemplo viola SRP pois a classe tem múltiplas responsabilidades: salvar e buscar. A maneira correta seria dividir essas funções para classes separadas como userRepository.
2️⃣ Open/Closed Principle (Aberto/Fechado)
Para adicionar novas funcionalidades sem modificar código existente, podemos criar interfaces ou abstrações:
// Antigo código:
class EmailService {
private final UserRepository userRepository;
@Autowired public EmailService(UserRepository userRepository) { this.userRepository = userRepository; }
public void sendWelcomeEmail(String emailId) { User user = userRepository.findByEmail(emailId); if (user != null && !user.isBlocked()) { String subject = "Bem-vindo!"; String body = "Olá! Seja bem vindo ao nosso site."; mailSender.send(subject, body, user.getEmail()); } else { throw new RuntimeException("Usuário não encontrado"); } } }
// Novo código com OCP aplicado: public interface UserService { void saveUser(User user);
Optional<User> getUserById(Long id); }
@Service public class UserServiceImpl implements UserService {
@Autowired public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; }
// Implementação do método de salvar usuário @Override public void saveUser(User user) { userRepository.save(user); }
// Implementação do método para buscar um usuário pelo ID
@Override
public Optional<User> getUserById(Long id) {
return userRepository.findById(id).map(u -> u.withoutSensitiveFields()); // Limpar campos sensíveis como senha
}
}
3️⃣ Liskov Substitution Principle (Substituição de Liskov)
O princípio da substituição de Liskov garante que subclasses devam poder substituir suas classes base sem quebrar o comportamento esperado. Por exemplo:
class Penguin extends Bird {
@Override
protected boolean canFly() {
return false;
}
// Outras implementações específicas de pinguins
}
Este é inválido porque a classe Penguin viola a garantia de que todas as instâncias de Bird podem voar.
Para corrigir, podemos separar os métodos e interfaces:
public interface FlyingBird {
default void fly() {}
}
public class Eagle implements FlyingBird {}
// A nova classe Pinguim não pode voar: public class Penguin implements Bird { private final String name;
public Penguin(String name) { this.name = name; }
@Override public void eat() { System.out.println("The " + name + " is eating."); }
@Override public void sleep() { System.out.println(name + " is sleeping"); }
@Override
public void swim() {
throw new UnsupportedOperationException();
}
}
4️⃣ Interface Segregation Principle (Segregação de Interfaces)
A ideia aqui é evitar forçar classes para implementarem métodos que elas nem usam. Por exemplo, uma classe Worker deve ser obrigada a implementar um método eat() se ela também precisa de outro método work():
public abstract class Worker {
protected boolean canEat();
class Human extends Worker { @Override protected boolean canEat() { return true; }
// Implementa outros métodos como work() }
class Robot extends Worker { @Override protected boolean canEat() { return false; }
// Implementa outros métodos como work()
}
Para aplicar o ISP, podemos criar interfaces menores e fazer as classes herdar apenas os métodos necessários.
5️⃣ Dependency Inversion Principle (Inversão de Dependência)
O princípio da inversão de dependência sugere que módulos de alto nível não devem depender diretamente de módulos de baixo nível. Em vez disso, ambos devem depender de abstrações ou interfaces genéricas. Isso ajuda na desacoplamento do código.
Exercício Prático: Central de Notificações
Vamos construir uma central de notificação com três tipos principais: email, SMS e push notifications. Vamos aplicar SRP, OCP e DIP para garantir que nosso sistema seja fácil de manter:
public interface NotificationService {
void sendNotification(Notification notification); }
@Service public class EmailNotificationServiceImpl implements NotificationService {
private final MailSender mailSender;
public EmailNotificationServiceImpl(MailSender mailSender) { this.mailSender = mailSender; }
@Override public void sendNotification(Notification notification) { String subject = "Notificação"; String body = "Olá! Aqui está sua notificação.";
if (!notification.isBlocked()) { // ISP - Interface Segregation Principle
mailSender.send(subject, body, notification.getRecipient());
} else {
throw new RuntimeException("Usuário bloqueado");
}
}
}
A classe EmailNotificationServiceImpl implementa a interface NotificationService, mas apenas usa os métodos necessários (sendNotification). Outras classes podem ser criadas para outras formas de notificação sem alterações no código existente.
Conclusão
Aplicando SOLID aos nossos exemplos práticos em Java, vimos como esses princípios ajudam a criar software mais robusto e mantinível. A chave é entender o contexto do problema e aplicar as melhores abstrações possíveis. O objetivo aqui não era memorizar regras rígidas, mas aprender a sentir quando um princípio foi violado e onde ajustar para melhorar o design.
Ideias aprendidas das fontes
- Entender cada princípio individualmente antes de aplicá-los ao código.
- Utilize interfaces genéricas (abstratas) para evitar dependências diretas entre módulos.
- Divida responsabilidades nas classes, garantindo que cada uma tenha somente uma razão para mudar.
- Polimorfismo seguro com decoradores (@abstractmethod) pode facilitar a aplicação dos princípios OCP e ISP.
- Desacoplar dependências usando abstrações ou interfaces ajuda na manutenção futura e evolução do sistema.
- Princípio da Inversão de Dependência (DIP) sugere desacoplamento, permitindo que novas funcionalidades sejam adicionadas sem alterações no código existente.
Referencias tecnicas
- [Fonte 1] Sandrolaxx/solid-examples
- [Fonte 2] SOLID explicado com exemplos em Java — do jeito simples que todo dev gostaria de ter aprendido - DEV Community
- [Fonte 3] Entenda SOLID de vez - Com exemplos simples em Java | Karen Santana | Arquitetura de Sistemas | Java | DIO
- [Fonte 4] Parte 2 - SOLID na Prática: Do Código Ruim ao Código Flexível | Biaggio Rizzo
- [Fonte 5] weritonpetreca/oo-solid-ninjas
- [Fonte 6] felipeNeves93/solidconcepts
- [Fonte 7] Desenvolvimento Orientado a SOLID - DEV Community
- [Fonte 8] Princípios SOLID em Java para Iniciantes - CodApp
- [Fonte 9] SOLID: Os Princípios que Você Acha que Domina (e os Erros que Comete)
- [Fonte 10] Princípios do SOLID: O que todo Desenvolvedor Java precisa saber ⚡🚀
- Contexto adicional fornecido pelo usuario