Entenda SOLID de vez - Com exemplos simples em Java
SOLID se refere a um acrônimo em inglês que descreve os cinco princípios de design de software em orientação a objetos para tornar o código mais compreensível, flexível e de fácil manutenção. Esses princípios, introduzidos por Robert C. Martin, se deram através do Movimento Ágil que queria garantir que a velocidade não afetasse a qualidade dos códigos, já que a partir da década de 60 houve um aumento muito grande de desenvolvedores sem background científico e sem conhecimento de boas práticas, o que trazia muita desorganização e fazia com que o código fosse de difícil manutenção.
Os cinco princípios do SOLID se referem a:
S - Single-Responsibility Principle - Princípio da Responsabilidade Única.
O - Open-Closed Principle - Princípio Aberto-Fechado
L - Liskov Substitution Principle - Princípio da Substituição de Liskov
I - Interface Segregation Principle - Princípio da Segregação de Interface
D - Dependency Inversion Principle - Princípio da Inversão de Dependência
Principio da Responsabilidade Única
O Princípio da Responsabilidade Única diz que:
"Nunca deve haver mais de uma razão para alterar uma classe. Em outras palavras, toda classe deve ter somente uma responsabilidade".
Ou seja, uma classe deve ter uma razão para mudar. Isso significa que cada classe deve ter uma responsabilidade ou função única. Isso faz com que o código seja mais fácil de manter.Vamos entender como isso funciona na prática.
Imagine que você quer construir uma casa. Para isso, você contrata uma pessoa para fazer a planta da casa, construir a estrutura, pintar as paredes e decorar o ambiente.Você percebe que essa pessoa está acumulando funções? O ideal seria que existisse uma pessoa para cada uma dessas atividades. É isso o que o Princípio da Responsabilidade Única exige. Vamos ver um exemplo agora com código.
Imagine a classe Employer abaixo:
É possível perceber que a Classe Employer (Funcionario) é responsável por criar um funcionário, calcular o salário e gerar um relatório. São muitas funções para uma classe só. Logo, nesse exemplo o código estaria infringindo o Princípio da Responsabilidade Única. A melhor forma seria dividir os métodos calculateSalary() e generateReport() em classes diferentes. Uma classe SalaryCalculator, responsável apenas por calcular o salário, e uma classe ReportGenerator, responsável apenas por gerar um relatório. Assim, cada classe vai ter sua própria função/responsabilidade.
Princípio Aberto-Fechado
O Princípio Aberto-Fechado diz:
"Objetos ou entidades devem ser abertos para extensão, mas fechados para modificações".
Em outras palavras, todas as entidades do projeto podem receber adições e novos recursos, mas não podem alterar o código que já foi escrito. Esse princípio evita que erros sejam criados em métodos e códigos que já existem e que estão funcionando corretamente.
Imagine que você tem uma classe Product que descreve produtos de uma loja e você deseja calcular o desconto sobre o produto, sabendo que naquele período do ano a loja não dá desconto. Você pode criar o método calcular desconto (calculateDiscount()) que sempre retorna zero. Entretanto, se agora a loja vai dar 20% de desconto para quem for cliente antigo e 50% de desconto para funcionário, o que fazer com o método de calcular desconto? Você teria que modificar a forma como você calcula o desconto, certo? Mas se você alterar o método calculateDiscount() estará violando o Princípio Aberto-Fechado, pois ele diz que entidades não podem ser alteradas. Para resolver isso, podemos usar uma interface que calcula desconto e criar classes que estendem dessa interface com métodos que possam calcular o desconto conforme a necessidade. Dessa forma não iremos alterar o método calculateDiscount() anterior. Vejamos como fica o código abaixo.
Principio da Substituição de Liskov
O Princípio da Substituição de Liskov diz:
"Se uma classe A é um subtipo da classe B, então você deve ser capaz de substituir toda ocorrência de B por A mantendo o mesmo comportamento."
Em outras palavras, uma classe Filha deve substituir uma classe Pai sem que isso acarrete em problemas no código. Tudo o que uma classe Pai faz, a classe Filha tem que fazer também. Por exemplo, se todo animal faz barulho, você espera que o cachorro, que é um animal, faça barulho. Ou seja, se uma classe superior (Animal) tem o comportamento de fazer barulho é necessário que a classe que é um subtipo dela (Cachorro) também tenha o comportamento de fazer barulho.
Princípio da Segregação de Interface
O Princípio da Segregação de Interfaces diz que interfaces mais específicas são melhores do que uma única interface geral. Ou seja, é melhor ter interfaces pequenas com métodos específicos, do que interfaces muito grandes cheia de métodos que uma classe terá que implementar, mas que não vai usar.
É como ter uma classe Pinguim que Implementa a interface Ave, nessa interface contém os métodos: comer, cantar e voar. A classe Pinguim não pode implementar a interface Ave, pois ela não vai possuir os métodos voar e cantar.
Logo, o melhor seria criar interfaces Comedor, Voador e Cantor. Dessa forma, a classe Pinguim vai implementar apenas métodos que ela vai usar.
Após a segregação de interface:
Principio da Inversão de Dependência
O Princípio da Inversão de Dependência diz:
"Os módulos de alto nível não devem depender diretamente de módulos de baixo nível. Ambos devem depender de abstrações. Além disso, abstrações não devem depender de detalhes, mas detalhes devem depender de abstrações".
Esse é um princípio difícil de explicar, mas pense que o principal objetivo é desacoplar as dependências diretas. Imagine que você tem uma classe chamada Interruptor que tem os métodos ligar e desligar, essa classe precisa da classe Lampada, ou seja está diretamente dependente dela. Isso não é bom, caso mudemos de Lampada para LampadaDeLed, ou algo parecido, teremos que reescrever o código da classe Interruptor.
Para desfazer essa dependência é necessário criar uma abstração, como uma interface chamada DispositivoControlavel. Agora Lampada vai implementar essa interface e Interruptor vai chamar essa interface ao invés de Lampada.
Assim, se eu quiser trocar Lampada por outro dispositivo eu não preciso alterar a classe Interruptor, pois ele não depende diretamente da Lampada, mas sim de uma abstração.
É claro que para associar corretamente é preciso rever e praticar bastante esses princípios. Uma boa referência é ler o próprio livro do Robert C. Martin para ter um entendimento mais completo sobre o assunto.
Bibliografia:
Código limpo: Habilidades práticas do Agile Software - Robert C. Martin. Editora Alta Books, 2009.
Manifesto Ágil : https://agilemanifesto.org/
*Codigos gerados no chatGPT