SOLID na Prática! (Parte 3: Liskov Substitution)

Thiago Barradas
thiagobarradas
Published in
4 min readJan 25, 2021
Vamos falar de SOLID, além do conceito! Vamos para a prática, agora com o princípio Liskov Substitution!

Este artigo é uma continuação de SOLID na Prática! (Parte 2: Open-Closed).

Liskov Substitution Principle

Objetos devem ser substituíveis por instâncias de seus subtipos (filhos), sem alterar a funcionalidade do programa. Deve ser capaz de afetar apenas a especificação da classe.”

Liskov é um princípio que traz as maiores dúvidas por parte dos desenvolvedores, mas, na minha visão, é um dos mais simples.

Se você tem um método que recebe um um tipo qualquer (exemplo uma classe Cadeira) e existem outros tipo no código que herdam de tipo (exemplo CadeiraDeBalanco), esse método que recebia o tipo original (tipo Pai), precisa receber seu subtipo (tipo filho, tipo que herda comportamento do tipo anterior) e funcionar normalmente. Para não violar os princípios de Liskov, a implementação no método não deve ter especificidades para o comportamento do pai ou do filho. Para ele, tanto faz. Ele vai apenas acionar os métodos que ele já conhecia do pai e tudo deve ocorrer bem.

Em um caso que você esteja utilizando o pai como uma classe abstrata, é impossível que uma instância dessa classe seja criada (uma característica de classes abstratas), e isso trás uma limitação que, quando o pai for um tipo aceito em um método, significa que qualquer filho (e somente filhos) serão recebidos por ali. Mas, o princípio continua válido entre irmãos. Todo e qualquer subtipo precisa ser substituido sem prejuízo no funcionamento do software e sem validações específicas para cada subtipo, permitindo um código. No exemplo citado anteriormente, CadeiraDeRodas deveria ser passado para o método fictício sem necessitar de nenhuma alteração.

Pensando no pai como uma classe abstrata, podemos pensar em uma abstração em que não faça sentido o pai ter uma instância independente dos subtipos, pois no mundo real também não faz sentido. Vamos imaginar a classe Felino e os subtipos Gato e Leão. Voltando ao exemplo da cadeira, no mundo real, você pode ter uma cadeira que seja apenas uma cadeira comum, sem ser de rodas ou de balanço. Mas para o felino isso não se aplica. Obrigatoriamente você precisa ter uma gato ou um leão. Um felino sem essa subdefinição, não existe. Logo, Felino seria uma classe abstrata, uma classe que não pode ser instanciada mas contém um comportamento prévio comum para todos os felinos, que podem (ou não) ser especificadas com implementações diferentes diretamente nos filhos.

Um método que receba o tipo Felino jamais receberá um felino puro, um felino que tenha sido criado com new Felino(...). Mas aceitaria o Gato e o Leão. Para ambos o software deveria funcionar sem necessidade de ajustes específicos no método que recebe o Felino.

Se você está achando confuso até agora, acredito que seja o momento para fortalecer seus estudos com orientação a objetos para depois avançar com os estudos de SOLID.

Veja um exemplo de uma abstração mal elaborada:

SOLID — Letra [L] — Exemplo de abstração errada.

O método AddTransaction funcionaria para Transaction ou CreditCardTransaction mas para PixTransaction uma exceção seria lançada, pois estamos partindo do princípio em nossa abstração que não existe juros para transações com Pix, apenas para cartão de crédito. Logo, a propriedade InterestAmount não faz parte da abstração, vide que é uma característica de uma transação de cartão de crédito apenas. Para o código acima funcionar, poderiamos adicionar um try catch no método AddTransaction ou no PixTransaction simplesmente retornar 0. Mas nossa abstração do mundo real em código, continuaria errada. Além disso, em nosso contexto, não faz sentido ter uma instância de Transaction, pois ela sozinha não corresponde nada para essa aplicação.

Vamos refazer o código acima tornando Transaction abstrata e InterestAmount (juros) uma propriedade unicamente de CreditCardTransaction. Tentaremos fazer com que o código funcione, ainda que com uma implementação errada no método AddTransaction para simular outra violação do princípio de Liskov.

SOLID — Letra [L] — Segundo exemplo de abstração errada.

No exemplo acima, o método AddTransaction na classe Order recebeu uma validação que entenda que uma transação é de cartão de crédito, para então somar seu juros ao valor total. Isso tira a capacidade da abstração e facilidade na manutenção do código, assim como a facilidade de extender o código, como visto no Open-Closed.

SOLID — Letra [L] — Exemplo de abstração correta.

O exemplo acima mostra um código que não fere o princípio de Liskov. Conseguimos ter os métodos e propriedades necessários em cada camada, sem necessidade de códigos específicos para o pai ou filhos no método que precisa utilizá-los.

Existe um ponto importante, principalmente para linguagens fracamente tipadas, que é o tipo de retorno. É importante garantir que em toda e qualquer situação, um método, no pai ou nos filhos, sempre tenha o mesmo tipo de retorno. Isso consegue ser ferido facilmente em linguagens como JavaScript e PHP. Mas também é possível ferir drasticamente usando C#, linguagem dos exemplos deste artigo. Um retorno do tipo object facilitaria esse tipo de gambiarra, ainda mais se quem chama o método precisa usar o seu retorno e com isso faz diversas manipulações, conversão ou validação de tipo.

Quando aplicamos o princípio da substituição de Liskov conseguimos aproveitar mais profundamente os conceitos da orientação a objetos, ganhamos mais confiança para aplicar polimorfismo, facilitamos a aplicação de outros princípios do SOLID e temos uma abstração do mundo real com uma modelagem muito melhor!

No próximo artigo, falaremos sobre Interface Segregation Principle (Princípio da segregação de interface). Lembre-se sempre: Interface é um dos componentes mais importantes da orientação a objetos para bons códigos, reaproveitaveis, desacoplados e evolutivos!

--

--

Thiago Barradas
thiagobarradas

Microsoft MVP, Elastic Contributor. Entusiasta de novas tecnologias e arquiteto de software na Stone. Cultuador do hábito de formar cabeças pensantes.