Princípios SOLID
Tags: java, braziliandevs, softwaredevelopment, solidprinciples
Published: 2023-10-25 13:56:10

SOLID é um acrônimo que representa cinco princípios de design de software que visam criar código mais compreensível, flexível e fácil de manter.
 
Esses princípios se baseiam nos paradigmas de coesão, encapsulamento e acoplamento da orientação a objetos:

- Coesão: A classe só tem atributos/responsabilidades que estejam relacionadas à ela. Uma classe "Funcionário" só vai ter informações referentes ao funcionário, uma única responsabilidade.

- Encapsulamento: Proteger uma classe contra manipulações externas. Por exemplo, fazer validações nos métodos da classe, que irão protegê-la de fazer alterações indevidas e violações da regra de negócio do sistema. Para fora da classe, devemos fornecer acesso apenas ao que é necessário em nossas classes.

- Acoplamento: Uma classe utilizar outra, a dependência entre classes. A própria classe deve implementar os métodos que necessitam chamar outras classes/métodos, isso não deve ser manual de fora da classe.

Com esses princípios esclarecidos, vamos entender as letras do acrônimo SOLID:

<u>S - Single Responsibility Principle</u>
"Uma classe deveria ter apenas um único motivo para mudar." Robert Martin

O Princípio da Responsabilidade Única (SRP) está relacionado às responsabilidades de uma classe e está muito ligado à coesão. Coesão significa coerência de pensamento ou de um todo. 
No exemplo da classe “Funcionário”, vamos pensar no salário do funcionário. Salário é algo que todo funcionário tem, mas é uma responsabilidade do funcionário gerenciar o salário? Essa responsabilidade está mais atrelada ao domínio que cuida de salários, por exemplo, Folha de Pagamento ou RH. O funcionário apenas possui um salário atrelado à ele, mas não é responsabilidade dele gerenciar esse salário. 
Vamos pensar por exemplo se surgir uma alteração nos impostos debitados de um salário, ou no fechamento do mês quando fosse contabilizado os dias trabalhados, ou ainda nos períodos de férias…Cada vez que isso acontecesse teríamos que alterar a classe Funcionario. Também teríamos que alterá-la quando um funcionário mudasse de endereço, incluísse novos dependentes etc. Seriam muitas responsabilidades diferentes em uma classe só, o que leva à perda de coesão. 

Portanto, o princípio da responsabilidade única se baseia na definição de responsabilidades. É necessário conhecer bem o negócio e o domínio da aplicação para separarmos as responsabilidades de cada domínio. 

Para analisarmos se uma classe atende ao SRP, precisamos responder a duas perguntas:

- Quais as responsabilidades dessa classe?

- Quais os motivos para essa classe ser modificada?

Se a classe possui muitos métodos diferentes, é modificada com frequência, não para de crescer e depende de muitas classes que não tem uma relação que faça sentido,  é provável que tenhamos uma classe com muitas responsabilidades diferentes.

<u>O - Open Closed Principle(OCP)</u>
Classes devem estar abertas para extensão, mas fechadas para modificação. Ou seja, é  recomendável evitar ficar alterando o código que já existe e ao invés disso adicionar os novos comportamentos quando necessário. Então, sempre que uma nova funcionalidade for implementada, o ideal é que possamos criar código novo e editar o mínimo possível de código já existente, pois ao editar código existente podemos acabar quebrando funcionalidades já implementadas e funcionais. 
Para isso acontecer, devemos garantir que cada ação/responsabilidade esteja na classe correta, pois quando muitos comportamentos diferentes são agrupados em uma única classe, é difícil não modificar o que já existe, e com isso podemos acabar usando muitas declarações condicionais para selecionar o comportamento correto. Encapsular o comportamento em classes de estratégia separadas pode ser uma estratégia para eliminar essas declarações condicionais. Um bom design OO vai ter poucas condicionais como if-else ou switch-case. 

<u>L - Liskov Substitution  Principle (LSP) </u>
O Princípio de Substituição de Liskov é  o princípio da substituibilidade. Ele define que subtipos devem ser substituíveis por seus tipos base. Quando usamos herança do jeito certo, deve ser possível usar qualquer método das classes filhas ao recebermos uma variável cujo tipo é o da classe mãe. 
Por exemplo, se temos uma classe Cachorro que estende de Animal, qualquer comportamento de cachorro deve poder ser substituído pelo tipo Animal. Em um subtipo podemos fazer mais coisas que a classe mãe e até alterar alguns comportamentos, mas não podemos fazer nada a menos.

Entretanto, devemos verificar a real necessidade de usar a herança, pois ela pode muitas vezes levar à intimidade excessiva. O uso de herança faz com que a classe filha tenha um forte acoplamento (ou intimidade) com sua classe mãe, e as subclasses sempre saberão mais sobre seus pais do que esses gostariam que elas soubessem. Por isso, é preferível usar a composição. 
No exemplo da classe Funcionário e Salário, todo funcionário possui um salário mas não deve ser de responsabilidade do funcionário gerenciar o salário. Podemos então ter uma referência de um objeto Salário na classe Funcionário, como atributo privado. O Funcionário então possui um Salário atrelado a ele, e qualquer mudança na classe Salário será feita diretamente nela, e o Funcionário poderá usufruir dos seus métodos públicos.

<u>I - Interface Segregation Principle (ISP)  
</u>O princípio da segregação de interface prega que devemos depender de interfaces e não de implementações. Devemos possuir interfaces coesas, cujos comportamentos são simples e bem definidos, pois classes que dependem de interfaces leves sofrem menos com mudanças em outros pontos do sistema.


Vamos considerar um cenário em que estamos trabalhando com dispositivos eletrônicos que podem ser ligados e desligados remotamente. Podemos criar interfaces segregadas para garantir que os dispositivos só precisem implementar os métodos relevantes para suas funcionalidades. Neste exemplo, teríamos a interface DispositivoLigavel e as classes Lampada e Televisao implementariam essa interface, cada uma delas representando um tipo diferente de dispositivo.
A classe ControleRemoto recebe qualquer dispositivo que implemente a interface DispositivoLigavel. Isso ilustra o princípio da segregação de interface, pois as implementações de Lampada e Televisao podem ser usadas de forma polimórfica através da interface comum DispositivoLigavel. Dessa forma, o código do controle remoto não precisa se preocupar com os detalhes específicos de cada dispositivo, apenas com os métodos definidos na interface, garantindo assim a aplicação do ISP.

<u>D - Dependency Inversion</u> 
O Princípio da Inversão de Dependência estabelece que módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. 
Além disso, abstrações não devem depender de detalhes. Em vez disso, os detalhes devem depender das abstrações. 
Com isso, uma classe não deve ser obrigada a implementar métodos de determinada interface que não fazem sentido para ela. Em outras palavras, o DIP sugere que as dependências de um sistema devem ser invertidas, tornando o sistema mais flexível, modular e fácil de manter.

Por exemplo, suponha que estamos construindo um sistema de notificação onde temos uma classe Notificador que é responsável por enviar mensagens. Inicialmente, poderíamos implementar a classe Notificador para enviar e-mails, criando uma dependência direta entre o módulo de alto nível (que usa a classe Notificador) e o módulo de baixo nível (a implementação do e-mail).
Neste exemplo, a classe SistemaDePedido teria uma dependência direta da implementação de e-mail da classe Notificador. Isso viola o DIP porque o módulo de alto nível (SistemaDePedido) está diretamente ligado ao módulo de baixo nível (Notificador).
Para aplicar o DIP, podemos introduzir uma abstração, como uma interface MeioDeComunicacao, que define um método enviarMensagem. Em seguida, tanto Notificador quanto SistemaDePedido dependem da abstração em vez de dependerem diretamente um do outro.
Assim, Notificador implementaria a interface MeioDeComunicacao, garantindo que ambas as classes dependam da abstração em vez de dependerem diretamente uma da outra. Isso torna o sistema mais flexível, pois podemos introduzir diferentes meios de comunicação (como SMS, notificações push, etc.) sem modificar o SistemaDePedido, desde que eles implementem a interface MeioDeComunicacao.
Assim, o Princípio da Inversão de Dependência (DIP) ajuda a reduzir o acoplamento entre os módulos, tornando o sistema mais modular, fácil de manter e extensível.


Os princípios SOLID são amplamente usados por desenvolvedores de software para criar sistemas mais robustos, flexíveis e fáceis de manter. Seguir os princípios SOLID ajuda a melhorar a qualidade do código, facilita a manutenção e favorece a extensibilidade do software.

Referências: Livro "Desbravando SOLID -Práticas avançadas para códigos de qualidade em Java moderno" do Alexandre Aquiles.