SOLID no Laravel - Aplicando princípios e boas práticas para entregar melhores soluções.
Thiago Luna
Posted on November 3, 2021
Olá pessoal!
Neste artigo, trago um assunto bastante importante e presente no nosso dia a dia como desenvolvedores e que, inclusive, tem sido bastante requisitado em entrevistas de emprego, sobretudo para as vagas de Sênior: Princípios do SOLID.
SOLID é um acrônimo para os 5 princípios da programação orientada a objetos e design de código teorizados por Robert C. Martin (Tio Bob): Single responsability; Open-closed; Liskov substitution; Interface segregation; Dependency inversion.
Existe bastante conteúdo na internet e meu objetivo neste artigo não é explicar detalhadamente cada princípio, mas mostrar alguns exemplos de como podemos usá-los para tonar nosso código de fácil entendimento, testável e de fácil manutenção.
Vou utilizar nos exemplos o PHP usando o framework Laravel.
Vamos começar com o S
1. Princípio da Responsabilidade Única
Este provavelmente é o mais conhecido. Diz que toda entidade, toda classe ou todo método deve fazer apenas uma tarefa, ter apenas uma responsabilidade, apenas uma razão para ser modificado.
No Laravel, ao invés do Controller fazer todas as tarefas, ele deve ser responsável por controlar a requisição, recebendo os parâmetros, chamando o método necessário de classes externas e, então, retornar o resultado ou fazer um redirecionamento.
E um bom exemplo disso é não ter os Controllers carregados de tarefas.
Na imagem acima, podemos observar 2 métodos de um Controller, onde ambos têm apenas a responsabilidade de distribuir as tarefas. No caso da function quotation, a responsabilidade de validação dos dados é do FormRequest. A tarefa principal fica sob a responsabilidade do método getQuotatiion que está em um serviço (classe externa). O envio de email para o usuário fica sob a responsabilidade de um outro serviço externo disparado por um Job. E, por fim, o Controller retorna para a view uma mensagem.
Dessa forma separamos as responsabilidade e não temos mais um Controller enoooorme. Outra vantagem é termos um código menos complexo e mais fácil de entender e manter.
2. Princípio do Aberto/Fechado
Este princípio diz que entidades de software, sejam elas classes, módulos, funções, etc, devem ser abertas para expansão e fechadas para modificação. Um bom exemplo disso são as bibliotecas ou os pacotes que instalamos via composer. Tudo que for instalado na pasta Vendor permanece na pasta Vendor e não pode ser modificado.
Como exemplo: digamos que eu instalei um pacote para conversão de moedas e que em algum momento vou precisar da descrição da moeda. Na imagem ao lado temos a classe Currency que possui somente 1 atributo. E agora? Altero a classe na pasta Vendor? #sqn
De acordo com este princípio vamos estender a classe Currency para, aí sim, termos a liberdade de fazer o que for necessário.
Agora podemos definir uma descrição para a moeda através dessa nova classe que estendeu a classe original.
Com isso, permanecemos fieis a este princípio, mantendo a classe Currency fechada para modificações e aberta para expansões.
Vantagens? Sim! Facilidade para criar novos comportamentos e dar manutenção.
Próximo princípio:
3. Princípio da Substituição de Liskov
Este princípio criado pela Barbara Liskov pode ser um pouco difícil de se entender no começo, mas podemos dizer que as classes devem ser intercambiáveis. Se temos 2 classes fazendo a mesma coisa, um cálculo, por exemplo, devemos ser possíveis de trocar uma classe pela outra sem parar a aplicação. Podemos, então, pensar que todos os parâmetros, tipos de retorno e exceções esperadas devem ser idênticos em ambas classes.
Neste exemplo, temos 2 classes que calculam a taxa para pagamento em diferentes meios de pagamento. Segundo Liskov, podemos chamar o método calculate() de qualquer classe passando o mesmo parâmetro (float) e obtendo o mesmo retornor (float) sem quebrar a aplicação.
Para a aplicação deste princípio, Type Hinting e Return Type são fundamentais, permitindo que as classes sejam verdadeiramente intercambiáveis.
As vantagens aqui são código facilmente reutilizável, mais fácil de se ler, entender e manter.
4. Princípio da Segregação de Interfaces
Vemos aqui que classes não devem ser forçadas a depender de métodos que não são utilizados. Já teve que declarar algum método e colocar apenas um "//" ou comentário dentro dele só porque precisou usar um outro método de uma Interface implementada na sua classe?
Pois é, este princípio orienta a segregar, separar métodos de uma mesma interface em novas interfaces com métodos específicos.
No exemplo acima, podemos observar que a classe ReportActivatedCurrencies precisa apenas usar o método getData() da interface ReportInterface. Contudo, é obrigada a declarar o método download também, mesmo sem utilizá-lo.
“Muitas interfaces específicas são melhores do que uma interface única.”
Já no exemplo abaixo, veremos como o princípio Interface Segregation trata essa questão.
Agora temos 2 interfaces distintas, cada uma contendo seu próprio método, e a classe concreta que precisar utilizar algum desses métodos implementará apenas a interface que contém o método.
O próprio Laravel utiliza bastante este princípio, como por exemplo as interfaces Jsonable, Responsable e Htmlable.
5. Princípio da Inversão de Dependência
Basicamente vemos aqui que não devemos depender de implementações, mas sim de abstrações, pois elas sofrem bem menos alterações ou praticamente nenhuma. Na prática, as classes devem depender de Interfaces e não de outras classes concretas.
Na imagem abaixo, podemos observar um exemplo da classe concreta CurrencyService que, através da injecçao de dependência, depende de uma interface, que nesse caso é o CurrencyRepositoryInterface.
Os métodos dessa interface utilizam o Eloquent ORM para operações que envolvem acesso ao banco de dados.
Se por algum motivo eu tenha que utilizar o Doctrine, ao invés do Eloquent, basta apenas criar uma nova classe concreta com os métodos utilizando o Doctrine e, no Laravel, alterar o bind da interface com essa nova classe concreta.
Dessa forma, com esse princípio aplicado, a classe CurrencyService não sofrerá nenhuma alteração. Tudo continuará do mesmo jeito, pois ela depende da Interface e não da classe concreta que implementa os métodos. Um detalhe é que as 2 classes concretas (CurrencyEloquentRepository e CurrencyDoctrineRepository) precisam ter os métodos exatamente iguais.
Neste exemplo vemos também o Repository Design Pattern em ação.
Conclusão
Seguindo esses princípios podemos entregar um código mais adaptável às mudanças de escopo, de fácil entendimento e manutenção, testável e bem mais reaproveitável no futuro.
Quando codamos precisamos perguntar a nós mesmos 2 coisas: quem e como alguém usará meu código no futuro? O que pode mudar no futuro?
Digamos que daqui a algum tempo seja preciso alterar o gateway de pagamento da empresa X para a Y. A implementação já feita está de fácil entendimento? Será fácil para outro Dev alterar o meio de pagamento?
Os princípios do SOLID nos ajudam com isso.
No entanto, como o próprio Tio Bob disse, vale ressaltar que esses princípios não são uma regra, mas sim um norte que contribuirá para a entrega de um bom código.
"Os princípios SOLID não são regras. Eles não são leis. Eles não são verdades perfeitas. São declarações na ordem de "Uma maçã por dia mantém o médico longe." Este é um bom princípio, é um bom conselho, mas não é uma verdade pura, nem uma regra.” - Tio Bob
E com isso chegamos ao final deste artigo com exemplos práticos do SOLID e espero que tenham te ajudado a entendê-lo um pouco melhor.
Há braços e até o próximo artigo.
Thiago Luna - Linkedin
Posted on November 3, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.