Princípios SOLID em Java

Princípios SOLID em Java

Criar código de alta qualidade é, sem dúvida, a missão de todo desenvolvedor que se preocupa com sua aplicação. Existem muitas práticas recomendadas que podem ajudar com isso. Vamos explicar neste artigo sobre os Princípios SOLID e como descrevem algumas maneiras simples para você aperfeiçoar a forma como você programa em linguagem de Programação Orientada a Objetos (POO). Neste artigo, vamos te mostrar como aplicar estes princípios na programação orientada a objetos com Java.

SOLID refere-se a cinco princípios de design em POO identificados por Robert C. Martin (conhecido na comunidade como Uncle Bob) por volta dos anos 2000.  Mas foi Michael Feather que criou o acrônimo após observar que esses princípios poderiam se encaixar em um único termo: SOLID.

  • Single Responsibility Principle (Princípio da Responsabilidade Única);
  • Open Close Principle (Princípio Aberto Fechado);
  • Liskov Substitution Principle (Princípio da substituição de Liskov);
  • Interface Segregation Principle (Princípio da Segregação da Interface);
  • Dependency Inversion Principle (Princípio da Inversão de dependência).

No geral, esses princípios ajudam os desenvolvedores a escrever código mais legível e de fácil manutenção, evitando o acoplamento de código em que classes e objetos se tornam dependentes uns dos outros.. Eles também garantem que o software seja modular, fácil de entender, depurar e refatorar.

Portanto, primeiro, vamos iniciar mostrando para que serve o Java para depois explorar cada um desses princípios, com exemplos, e porque devemos considerá-los importantes ao desenvolver uma aplicação em Java ou em outra linguagem POO.

Para que serve o Java?

Java é uma das linguagens de programação mais populares para a construção de aplicações e plataformas web. Sendo projetado para ser flexível, o Java permite que desenvolvedores escrevam código executável em qualquer máquina, independentemente da arquitetura ou plataforma.

Além disso, ele também é usado em tecnologias do lado do servidor, como Apache, JBoss, GlassFish, etc. Além disso, existem muitos recursos Java para análise de big data e para aplicações de Computação Científica.

Java é uma linguagem de POO, o que significa que nela tudo é um objeto. É uma linguagem multithread com gerenciamento automático de memória. Isto é, aplicações Java podem armazenar uma grande quantidade de dados em tempo de execução, os quais podem ser usados ​​para verificar e resolver acessos a objetos em tempo real.

Tudo isso significa que a linguagem continua sendo relevante atualmente. Entretanto, vale lembrar que para implementar os princípios SOLID em Java, você precisa entender conceitos básicos de OOP como herança, polimorfismo, abstração e encapsulamento.

S - Princípio da Responsabilidade Única

Como poderíamos esperar, esse princípio afirma que uma classe deve ter apenas uma responsabilidade. Ou seja, cada classe ou estrutura semelhante no código da sua aplicação deve fazer apenas um trabalho.

Portanto, isso significa que uma classe deve ter um, e somente um, motivo para mudar, podendo também ser aplicado a funções, componentes, entidades, etc.

É muito comum violarmos esse princípio ao criarmos classes que fazem de tudo, conhecidas como “God Class”. Em algum momento, torna-se difícil realizar uma alteração nessa classe sem comprometer outras partes do sistema.

Vamos ver um exemplo de código que não utiliza o princípio da responsabilidade única em Java:

De início, parece não ter nenhum problema, mas aqui temos lógica de autenticação do usuário e lógica de validação de cargo misturados.  Vamos supor que ocorra um problema com o método “temCargo”, não só os usuários não vão conseguir realizar o login no sistema, mas todas as funções relacionadas a esse método deixarão de funcionar, assim como o método “usuarioValido”.

Solucionando o problema de acordo com o princípio da Responsabilidade Única:

Observe que agora cada responsabilidade foi separada em uma classe, tornando mais fácil a sua alteração e teste.

O – Princípio Aberto-Fechado

O Princípio Aberto-Fechado afirma que as classes ou objetos devem ser abertos para extensão, mas fechados para modificação. Ok, mas o que isso significa na prática?

“Aberto para extensão” diz que você deve projetar suas classes para que novas funcionalidades possam ser adicionadas à medida que novos requisitos são gerados.

“Fechado para modificação” significa que uma vez que uma classe tenha sido desenvolvida ela nunca deve ser modificada, exceto para corrigir bugs e demais problemas no código.

Em suma, quando novos comportamentos e funções precisam ser adicionados ao software, devemos estender em vez de alterar o código original.

Exemplo de uma classe que representa contratos de funcionários:

Imagina que esse código tenha sido desenvolvido antes da reforma da legislação trabalhista e novas formas de contrato passam a se tornar mais comuns, como o PJ. Se a empresa for adotar novas formas de contrato, seria necessário modificar a classe “FolhaDePagamento” e como resultado o princípio Aberto-Fechado seria quebrado.

Uma possível solução seria:

Como resultado, a classe “FolhaDePagamento” não precisa mais saber quais métodos chamar para calcular o pagamento. Enquanto a interface "Remunerável" estiver implementada, ela poderá calcular corretamente a remuneração de qualquer novo tipo de funcionário criado no futuro sem precisar alterar seu código-fonte.

L - Princípio da substituição de Liskov

Este princípio leva esse nome por ter sido criado por Barbara Liskov, em 1988, a qual faz a seguinte definição formal:

“Se para cada objeto o1 do tipo S há um objeto o2 do tipo T de forma que, para todos os programas P definidos em termos de T, o comportamento de P é inalterado quando o1 é substituído por o2 então S é um subtipo de T”

Isto é, classes derivadas podem ser substitutas de suas classes base, ou ainda: toda e qualquer classe derivada pode ser usada como se fosse a classe base. Exemplo:

Por que esse princípio é necessário? Atendendo a esse princípio, você garante que a classe derivada seja usada de forma transparente onde a classe base é vista. Assim, todo código que depende da classe base poderá usar qualquer uma das derivadas em tempo de execução mesmo sem saber da existência delas.

Portanto, com esse princípio em mente atente-se a sua hierarquia de classes e faça bom uso do polimorfismo.

I - Princípio da Segregação da Interface

De acordo com esse princípio, uma classe nunca deve ser obrigada a implementar interfaces e métodos que ela não usará. De modo geral, esse princípio afirma que é melhor construir interfaces pequenas e específicas  em vez de interfaces genéricas.

Exemplo de como algumas aves são tratadas em um jogo em Java:

Fica evidente como atribuímos comportamentos genéricos para todas as demais classes. A Classe Pinguim, por exemplo, acabou sendo forçada a ter o método “setAltitude”, situação que não deveria acontecer pois os pinguins não voam.

Para isso, recomendamos a seguinte interface mais específica, permitindo isolar os comportamentos das aves de maneira correta:

D - Princípio da Inversão de dependência

Esse princípio afirma que, primeiro, módulos de alto nível não devem depender de módulos de baixo nível, eles devem depender de abstrações; e segundo que as abstrações não devem depender de detalhes; detalhes devem depender de abstrações.

A ideia é que isolemos nossa classe atrás de um limite formado pelas abstrações que dela dependem. Se os detalhes por trás dessas abstrações mudarem, nossa classe ainda continuará segura. Isso ajuda a manter o acoplamento baixo, além de tornar nosso projeto mais fácil de ser alterado, permitindo testar coisas isoladamente.

Vejamos um exemplo em Java que isso não acontece, onde a classe Carro depende da classe Engine:

O código irá funcionar, mas e se quisermos adicionar outro tipo de motor, digamos um motor a diesel? Isso exigirá a refatoração da classe Carro. No entanto, podemos resolver isso adicionando uma camada de abstração, utilizando uma interface:

Agora podemos conectar qualquer tipo de Engine que implemente a interface Engine à classe carro:

Conclusão

O uso desses princípios permanece relevante no contexto atual. Com o SOLID você garante que seu programa seja modular, compreensível, depurável e refatorável. Além disso, facilita o aprendizado e a assimilação de conceitos e ideias por meio desses exemplos de linguagem baseados em Java, podendo ser utilizados para outras linguagens de POO. Portanto, seguir o SOLID ajuda desenvolvedores a economizar tempo e esforço no desenvolvimento e manutenção de suas aplicações.

🛑
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo. A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.