No desenvolvimento de software, a manutenção, extensão e a flexibilidade do código são importantes para o sucesso a longo prazo de um projeto. Os princípios SOLID foram formulados para orientar os desenvolvedores na criação de código que seja mais fácil de entender, modificar e estender. Neste artigo, vamos falar de cada um dos cinco princípios SOLID e como usar com exemplos práticos em Java.
O Princípio da Responsabilidade Única (SRP) estabelece que uma classe deve ter apenas uma razão para mudar, ou seja, deve ter uma única responsabilidade dentro do sistema.
// Antes de aplicar o SRP class ProductService { public void saveProduct(Product product) { // Lógica para salvar o produto no banco de dados } public void sendEmail(Product product) { // Lógica para enviar um email sobre o produto } }
// Após aplicar o SRP class ProductService { public void saveProduct(Product product) { // Lógica para salvar o produto no banco de dados } } class EmailService { public void sendEmail(Product product) { // Lógica para enviar um email sobre o produto } }
No exemplo, separamos a responsabilidade de salvar um produto no banco de dados da responsabilidade de enviar e-mails sobre o produto. Isso facilita futuras mudanças, pois alterações no envio de e-mails não afetam mais a lógica de salvamento de produtos.
O Princípio do Aberto/Fechado (OCP) sugere que as entidades de software (classes, módulos, funções, etc.) devem estar abertas para extensão, mas fechadas para modificação. Isso é alcançado através do uso de abstrações e herança.
// Exemplo inicial violando o OCP class AreaCalculator { public double calculateArea(Rectangle[] rectangles) { double area = 0; for (Rectangle rectangle : rectangles) { area = rectangle.width * rectangle.height; } return area; } }
// Exemplo após aplicar o OCP interface Forma { double calculateArea(); } class Rectangle implements Forma { private double width; private double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double calculateArea() { return width * height; } } class AreaCalculator { public double calculateArea(Forma [] formas) { double area = 0; for (Forma formas: formas) { area = forma.calculateArea(); } return area; } }
Nesse segundo exemplo, inicialmente a classe AreaCalculator estava diretamente dependente da classe Rectangle. Isso significa que se você quisesse adicionar outro tipo de forma, como um círculo ou um triângulo, você precisaria modificar a classe AreaCalculator, violando assim o OCP. Com a criação da interface Forma, a classe AreaCalculator é capaz de receber novas formas geométricas sem modificar o código existente.
O Princípio da Substituição de Liskov (LSP) afirma que objetos de uma superclasse devem ser substituíveis por objetos de suas subclasses sem afetar a integridade do sistema. Em outras palavras, o comportamento das subclasses deve ser consistente com o comportamento das superclasses.
// Classe base class Bird { public void fly() { // Método padrão que imprime "Flying" System.out.println("Flying"); } } // Classe derivada que viola o LSP class Duck extends Bird { @Override public void fly() { // Sobrescrita que imprime "Ducks cannot fly" System.out.println("Ducks cannot fly"); } }
Problema: A classe Duck, está sobrescrevendo o método fly() para imprimir "Ducks cannot fly", assim alteramos o comportamento padrão definido na classe base Bird, que é de que todos os pássaros voam ("Flying"). Isso viola o LSP porque qualquer código que espera um objeto Bird ou suas subclasses para voar não funcionará corretamente com um Duck, que a gente já sabe que não voa.
// Classe derivada que respeita o LSP interface Bird { void fly(); } class Eagle implements Bird { @Override public void fly() { System.out.println("Flying like an Eagle"); } } class Duck implements Bird { @Override public void fly() { throw new UnsupportedOperationException("Ducks cannot fly"); } }
Com essa abordagem, Eagle e Duck podem ser permutáveis onde um Bird é esperado, sem quebrar as expectativas definidas pela interface Bird. A exceção lançada por Duck comunica explicitamente que patos não voam, sem modificar o comportamento da superclasse de uma maneira que possa causar problemas inesperados no código.
O Princípio da Segregação de Interfaces (ISP) sugere que as interfaces de uma classe devem ser específicas para os clientes que as utilizam. Isso evita interfaces "gordas" que obrigam implementações de métodos não utilizados pelos clientes.
// Exemplo antes de aplicar o ISP interface Worker { void work(); void eat(); void sleep(); } class Programmer implements Worker { @Override public void work() { // Lógica específica para programar } @Override public void eat() { // Lógica para comer } @Override public void sleep() { // Lógica para dormir } }
// Exemplo após aplicar o ISP interface Worker { void work(); } interface Eater { void eat(); } interface Sleeper { void sleep(); } class Programmer implements Worker, Eater, Sleeper { @Override public void work() { // Lógica específica para programar } @Override public void eat() { // Lógica para comer } @Override public void sleep() { // Lógica para dormir } }
No exemplo, dividimos a interface Worker em interfaces menores (Work, Eat, Sleep) para garantir que as classes que as implementam tenham apenas os métodos necessários para elas. Isso evita que as classes tenham que implementar métodos que não são relevantes para elas, melhorando a clareza e coesão do código.
O Princípio da Inversão de Dependências (DIP) sugere que módulos de alto nível (como classes de negócio ou de aplicação, que implementam as principais regras de negócio) não devem depender de módulos de baixo nível (classes de infraestrutura, como acesso a dados e serviços externos, que oferecem suporte às operações de alto nível). Ambos devem depender de abstrações.
// Exemplo antes de aplicar o DIP class BackendDeveloper { public void writeJava() { // Lógica para escrever em Java } } class Project { private BackendDeveloper developer; public Project() { this.developer = new BackendDeveloper(); } public void implement() { developer.writeJava(); } }
// Exemplo após aplicar o DIP interface Developer { void develop(); } class BackendDeveloper implements Developer { @Override public void develop() { // Lógica para escrever em Java } } class Project { private Developer developer; public Project(Developer developer) { this.developer = developer; } public void implement() { developer.develop(); } }
A classe Project depende agora de uma abstração (Developer) em vez de uma implementação concreta (BackendDeveloper). Isso permite que diferentes tipos de desenvolvedores (por exemplo, FrontendDeveloper, MobileDeveloper) possam ser facilmente injetados na classe Project sem modificar seu código.
Adotar os princípios SOLID não apenas eleva a qualidade do seu código, mas também fortalece suas habilidades técnicas, aumenta sua eficiência no trabalho e impulsiona sua trajetória profissional como desenvolvedor de software.
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3