SOLID é um acrônimo dos cinco primeiros princípios da programação orientada a objetos e design de código identificados por Robert C. Martin (ou Uncle Bob) por volta do ano 2000. O acrônimo SOLID foi introduzido por Michael Feathers, após observar que os cinco princípios poderiam se encaixar nesta palavra, as siglas significam:
Este princípio nos diz que “uma classe deve ter um, e somente um, motivo para mudar”, porém pode ser aplicado a funções, componentes, entidades, etc. Esse princípio declara que uma classe deve ser especializada em um único assunto e possuir apenas uma responsabilidade dentro do software, ou seja, a classe deve ter uma única tarefa ou ação para executar. Comumente quando estamos programando orientado a objetos, acabam violando este princípio e muitas vezes criamos classes que fazem de tudo, chamadas na literatura de “God Class”, inicialmente tudo deve funcionar bem, porém quando for necessário realizar uma alteração nessa classe, será difícil modificar umas de suas funcionalidades sem comprometer outras partes do sistema.
Abaixo temos um exemplo de código que não usa o princípio da responsabilidade única, quando olhamos inicialmente o código não encontramos nenhum problema, já que o método “UsuarioValido” trata algo relacionado ao usuário, o método “temCargo” trata do cargo de um usuário, porém vamos imaginar que ocorra um problema no método “temCargo”, não só os usuários não vão conseguir realizar o login no sistema, como todas as funcionalidade relacionada a este método irão parar de funcionar, e o mesmo ocorre com o método “usuarioValido”. Para este código pode-se dizer que a classe usuário tem pelo menos três motivos para mudar seus atributos getters e setters, o método “usuarioValido” e o método “temCargo”, ferindo o princípio da responsabilidade única, deixando a classe menos coesa e com um nível alto de acoplamento.
Uma possível solução para o problema acima é:
Note que agora cada responsabilidade foi separada em uma classe, pois se um problema ocorre em algumas destas partes, o problema não irá se espalhar pelo sistema, deste modo facilitando os testes, descobrimento de novos bugs e deixando a aplicação mais coesa e menos acoplada.
Este princípio nos diz que “Objetos ou entidades devem estar abertos para extensão, mas fechados para modificação”, ou seja, quando novos comportamentos e recursos precisam ser adicionados no software, devemos estender e não alterar o código fonte original. Abaixo temos um exemplo de classes que representam contratos de funcionários.
A classe “FolhaDePagamento” precisa verificar o funcionário para aplicar a regra de negócio correta na hora do pagamento. Supondo que a empresa cresceu e resolveu trabalhar com funcionários PJ, obviamente seria necessário modificar essa classe e consequentemente o princípio Open-Closed do SOLID seria quebrado. A modificação mais comum seria adicionar um IF e verificar o novo tipo de funcionário PJ, aplicando as regras para essa nova funcionalidade, mas é exatamente este o problema, ao alterar uma classe já existente para adicionar um novo comportamento, corremos um sério risco de introduzir bugs em algo que já estava funcionando. Abaixo temos uma possível solução para este problema.
Agora a classe “FolhaDePagamento” não precisa mais saber quais métodos chamar para calcular. Ela será capaz de calcular o pagamento corretamente de qualquer novo tipo de funcionário que seja criado no futuro,desde que ele implemente a interface “Remuneravel” , sem qualquer necessidade de alteração do seu código fonte. Este princípio é base para um dos design patterns mais conhecidos, o Strategy.
Este princípio nos diz que “Uma classe derivada deve ser substituível por sua classe base”, ou de maneira mais simples, se um objeto B é um subtipo de um outro objeto A, este objeto A pode substituir B em qualquer lugar no código, sem que este código pare de funcionar, veja o exemplo abaixo:
Estamos passando como parâmetro tanto a classe pai como a classe derivada e (entretanto) o código continua funcionando da forma esperada. Alguns exemplos de violação do princípio de Liskov são: sobrescrever/implementar um método que não faz nada, lançar uma exceção inesperada ou retornar valores de tipos diferentes da classe base. Para não violar o Liskov Substitution Principle, além de estruturar muito bem as suas abstrações, em alguns casos, você precisará usar a injeção de dependência e também usar outros princípios do SOLID, como por exemplo, o Princípio Aberto Fechado e o Princípio da Segregação da Interface, será abordado no próximo tópico.
Seguir o LSP nos permite usar o polimorfismo com mais confiança. Podemos chamar nossas classes derivadas referindo-se à sua classe base sem preocupações com resultados inesperados.
O princípio da segregação da interface nos diz que “uma classe não deve ser forçada a implementar interfaces e métodos que não irá utilizar”. Esse princípio basicamente diz que é melhor criar interfaces mais específicas ao invés de termos uma única interface genérica. O exemplo abaixo mostra como algumas aves são tratadas dentro de um jogo.
Percebam que ao criar a interface Aves, atribuímos comportamentos genéricos e isso acabou forçando a classe Pinguim implementar o método “setAltitude”, do qual ela não deveria ter, já que pinguins não voam. A solução recomendada então é criar uma interface mais específica.
No exemplo acima, retiramos o método “setAltitude” da interface Aves e adicionamos em uma interface derivada “AvesQueVoam”. Isso nos permitiu isolar os comportamentos das aves de maneira correta dentro do jogo.
Antes de tudo, vale lembrar que o termo inversão de dependência não deve ser confundido com o padrão de projeto injeção de dependência.
O princípio da inversão de dependência nos diz que devemos “Depender de abstrações e não de implementações“, Uncle Bob ainda fala que “módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender da abstração e abstrações não devem depender de detalhes, detalhes devem depender de abstrações”. O exemplo abaixo demonstra o princípio da inversão de dependência sendo quebrado.
No exemplo acima, podemos perceber que além de quebrar outros princípios do SOLID, a classe concreta Interruptor depende de uma outra classe concreta (Ventilador). O interruptor deveria ser capaz de acionar qualquer dispositivo independente de ser um ventilador, uma lâmpada ou até mesmo um carro. Uma possível solução para este problema seria:
Percebam que agora a classe concreta Interruptor depende da abstração de um “IDispositivo” e não mais de uma classe concreta.
-
Dificuldade na testabilidade / criação de testes de unidade;
-
Código macarrônico, sem estrutura ou padrão;
-
Dificuldades de isolar funcionalidades;
-
Duplicação de código, uma alteração precisa ser feita em N pontos;
-
Fragilidade, o código quebra facilmente em vários pontos após alguma mudança.
Redação sobre o design pattern Strategy, pode ser usada para resolver problemas com o Open Close Principle.
https://www.ufsm.br/pet/sistemas-de-informacao/2020/07/01/strategy/
Livro do Uncle Bob:
Robert C. Martins. Arquitetura Limpa: O guia do artesão para estrutura e design de software. Alta Books Editora, 2019.
Bruno Rossi – 10/02/2021