This content originally appeared on DEV Community and was authored by Wali Queiroz
Hoje vamos falar sobre o Liskov Substitution Principle. Para ver os artigos anteriores da série acesse:
- Princípios SOLID em GoLang - Single Responsability Principle (SRP)
- Princípios SOLID em GoLang - Open/Closed Principle (OCP)
Dentre os princípios SOLID, o LSP é o que tem a definição formal mais complicada. Por outro lado, é o de mais simples execução, porque o conceito é intuitivo e você acaba aplicando sem muito esforço cognitivo na maioria das vezes.
O princípio foi definido por Barbara Liskov da seguinte forma:
"Se q(x) é uma propriedade demonstrável dos objetos x de tipo T. Então q(y) deve ser verdadeiro para objetos y de tipo S onde S é um subtipo de T."
Complicado, né?
Mas, no fim das contas, essa definição matemática pode ser traduzida na seguinte sentença:
"Se um ObjetoX é uma instância da ClasseX, e um ObjetoY é uma instância da ClasseY, e a ClasseY herda da ClasseX— se usarmos ObjetoY em vez de ObjetoX em algum lugar do código, a funcionalidade não deve ser interrompida."
Como podemos ver, esse é um princípio que parece estar diretamente ligado aos conceitos de classe e herança, e nenhum dos dois está presente no Go. Então como podemos aplicar?
Já vi alguns artigos explicando o LSP em GoLang utilizando os recursos de composição (embedding) da linguagem, mas, do meu ponto de vista, não é uma boa abordagem, pois a composição não permite substituir a estrutura pai pela estrutura filha. Observem:
A definição do princípio leva muitos (eu incluso por bastante tempo) a pensar que se trata de somente de herança, mas na verdade trata-se de subtipagem. Logo, em Go, o LSP é melhor expresso através do uso de interfaces e polimorfismo. Vamos ao próximo exemplo:
Neste cenário, fizemos o método Refuel() na struct ElectricCar lançar um panic indicando que carros elétricos não podem ser abastecidos com gasolina.
Ao chamar a função PerformVehicleActions() com uma instância de ElectricCar, ocorre uma quebra óbvia do princípio de substituição de Liskov. Embora ElectricCar implemente o método Refuel() definido pela interface Vehicle, a implementação específica do carro elétrico interrompe a execução do programa.
No exemplo, vimos a quebra da funcionalidade da interface em vez de seguir a expectativa. E essa é a sacada do LSP em Go: Uma struct não deve violar o propósito da interface.
Poderíamos alterar o design de modo que o cliente do método Refuel() tenha que estar ciente de um possível erro ao chamá-lo. No entanto, isso significaria que os clientes teriam que ter conhecimento especial do comportamento inesperado do subtipo. Isso começa a quebrar o Open/Closed Principle.
Em resumo, toda violação do LSP se torna uma violação do OCP. Então, como corrigir?
Uma possível modificação para respeitar o princípio seria mudar a interface Vehicle para ter um método mais genérico, como Recharge, em vez de Refuel. Assim, cada subtipo pode implementar esse método de acordo com a sua fonte de energia, seja gasolina ou eletricidade.
Outra possível modificação seria criar uma interface separada para os veículos elétricos, como ElectricVehicle, que tenha um método específico para recarregar a bateria, como RechargeBattery. Assim, o ElectricCar implementaria essa interface e não haveria conflito com o método Refuel.
Aqui está um exemplo de código usando a segunda modificação:
Depois disso tudo vocês podem estar pensando: "Ah, Wali, com código de mentirinha é tudo muito fácil, quero ver no mundo real." Então vejamos um exemplo de aplicação do LSP no mudo real:
Neste exemplo, temos a definição da interface Cache, com os métodos Get, Set e Delete, e as implementações RedisCache e MemoryCache.
O UserService utiliza a interface Cache como dependência, permitindo a troca entre o RedisCache e o MemoryCache sem problemas.
No cenário apresentado, o LSP poderia ser quebrado se a implementação específica de algum método de Cache na struct derivada não cumprisse as mesmas garantias e pré-condições definidas pela interface.
Por exemplo, se a implementação do método Get em MemoryCache lançasse um erro diferente ou não respeitasse a garantia de retornar um erro quando a chave não é encontrada, isso quebraria o LSP. Da mesma forma, se a implementação do método Set em RedisCache não armazenasse corretamente os valores no Redis, ou a implementação do método Delete não excluísse corretamente as chaves, isso também violaria o LSP.
Isso é tudo, pessoal! Até a próxima!
Referências:
- Clean Coder Blog - SOLID Relevance
- Practical SOLID in Golang: Liskov Substitution Principle
- Baeldung - Liskov Substitution Principle in Java
This content originally appeared on DEV Community and was authored by Wali Queiroz
Wali Queiroz | Sciencx (2024-07-29T00:51:26+00:00) Princípios SOLID em GoLang – Liskov Substitution Principle (LSP). Retrieved from https://www.scien.cx/2024/07/29/principios-solid-em-golang-liskov-substitution-principle-lsp/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.