SOLID além do "S": A importância dos demais princípios
Ultimamente tenho participado de dinâmicas técnicas com programadores de diversos níveis e percebi algo em comum entre a maioria quando falamos sobre SOLID. Nas conversas, sempre nos aprofundamos muito no princípio "S", mas poucas vezes conseguimos explorar os demais princípios além do Single Responsibility Principle.
Fazendo mea culpa, acredito que o SRP, além de ser o primeiro, é mais auto explicativo entre os demais, por isso a facilidade de entendimento e de lembrar seu objetivo.
Sendo assim, decidi escrever esse post para irmos juntos um pouco além do SRP e entender o objetivo dos demais princípios e como eles podem nos ajudar a escrever códigos melhores na prática.
Single Responsibility Principle (SRP)
O principio de responsabilidade única tem como objetivo garantir que sua classe faça somente o necessário dentro do seu domínio. Em poucas palavras, fazer o que ela realmente deve fazer, nada além disso!
Coloquei um exemplo simples (do que não devemos fazer) abaixo, apenas para fixarmos o conteúdo.
Open/Close Object Principle (OCP)
O próximo é o princípio aberto/fechado. Meio esquisito, né? Vamos entender melhor a definição dele:
Objetos ou entidades devem ser abertos para extensão, mas fechados para modificação.
Tá! Mas o que isso quer dizer afinal de contas? Basicamente, sua classe deve ser extensível de forma que não precise de modificações para tal.
Geralmente, a maioria dos artigos utilizam o cálculo de formas geométricas para exemplificar a aplicação deste princípio. Decidi mudar um pouco e utilizar um sistema de notificação de delivery.
Suponhamos que temos uma classe responsável por avisar os usuários sobre seus pedidos feitos na loja de Donuts do Sr Jonas. Inicialmente, as entregas de delivery eram feitas de carro, então foi criada apenas uma classe para calcular o tempo de entrega até o endereço do cliente.
Até ai tudo bem. Mas o filho do Sr Jonas comprou um drone e eles tiveram a ideia de fazer as entregas com ele. Como ficaria o código?
Imaginemos que dentro de cada condição dessa, existe uma regra de negócio para calcular o tempo médio da entrega, ok? Então, se amanhã o Sr Jonas tiver a brilhante ideia de realizar as entregas com bicicleta, teríamos que incluir mais um if e já sabemos onde isso vai parar né?!
Para que não passemos por maus bocados no futuro, poderíamos fazer o seguinte:
Desta forma, sempre que surgir um novo modal para as entregas, precisaríamos "apenas" criar uma classe e passa-lá como injeção de dependência para a classe responsável por avisar sobre a entrega. Isso torna nossa classe de notificação fechada para modificações, porém aberta para novas extensões. Assim fica mais simples o entendimento, não?
Liskov Substitution Principle (LSP)
O princípio de substituição de Liskov diz:
“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”
Resumindo, toda classe derivada deve ter a possibilidade de ser usada como a classe base!
Para esse exemplo, vamos imaginar uma software que faz a gestão de manutenção de veículos diversos:
Podemos ver que a classe de manutenção espera uma instância de Veiculo(), no entanto passamos a instância de uma classe derivada e o comportamento deve ser o mesmo.
Interface Segregation Principle (ISP)
Resumidamente, esse princípio diz:
Nenhuma classe deve ser forçada a depender de interfaces que elas não usam/precisam
Como todos os outros, é muito mais fácil explicá-lo com código. Vamos entender o que o ISP resolve.
No exemplo acima, a classe Avestruz implementa a interface Aves, porém, mesmo eu, com meu pífio conhecimento sobre animais (adquiridos no Animal Planet), apesar de serem aves, sei que os avestruzes não voam. Logo, não seria pertinente essa classe implementar a interface Aves.
Para solucionarmos isso, podemos fazer o seguinte:
Com isso, garantimos que as classes não serão forçadas à criar métodos apenas para simplesmente satisfazer as interfaces.
Dependency Inversion Principle (DIP)
Enfim, chegamos no último princípio. O princípio da injeção de dependência!!
Não, pera...
É muito comum fazermos confusão e achar que o princípio DIP quer dizer injeção de dependência, mas na verdade trata-se do princípio de inversão de dependência. Mas está tudo bem, acontece com as melhores famílias.
Vamos entender qual problema esse princípio resolve. Para entendermos, nada melhor que usarmos a definição do Uncle Bob
Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações;
Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.
Confesso que, na primeira vez que li, não entendi muito bem. Então, para pacificarmos o entendimento, vamos entender o que são esses módulos/detalhes:
- Módulo de alto nível: São as implementações de fácil entendimento. Aquelas que, com pouco esforço mental, o programador consegue discernir o propósito.
- Módulo de baixo nível: Ao contrário das implementações de alto nível, essas são as que exigem mais raciocínio para compreender. Seja uma implementação que exige regras relacionadas à cálculos matemáticos complexos, por exemplo.
- Detalhes: São as implementações que não necessitam estarem diretamente relacionadas com a arquitetura do sistema, mas são essenciais para alguns processos. Seja a persistência num banco, publicação de mensagem em uma fila e etc.
Entendido tais pontos, vamos ao exemplo que fere o DIP:
No exemplo acima, a classe de Transaction está totalmente acoplada com a conexão do banco. Além de violarmos o DIP ainda violamos o OCP, pois nossa classe fica totalmente dependente de uma conexão MySQL e, no caso de uma mudança de SGBD, precisaríamos alterar nossa classe de transações.
Para nos adequarmos ao DIP precisamos fazer que nossa classe Transaction dependa apenas de uma abstração. Sendo assim, podemos solucionar da seguinte forma:
Pronto! Assim, tiramos o acoplamento da nossa classe de transações com o banco. Isso nos permite utilizar outros drivers, sem a necessidade de grandes alteração em nossa classe (OCP) e, de bônus, ainda garantimos que nossa classe não tem dependência de detalhes (a conexão com banco, nesse caso).
Espero realmente que esse artigo te ajude de alguma forma a ter um entendimento mais aprofundado sobre os princípios e, quando o assunto for sobre SOLID, vá além do "S".
Obrigado por ler até aqui!
Publiquei esse mesmo artigo em meu LinkedIn, caso queira interagir lá