O terceiro princípio é o Liskov Substituition Principle. Ele foi definido por Barbara Liskov em 1987 e, embora que por definição academia pareça ser um “bicho de setes cabeças”, na prática é bem simples e ideal para criar sistemas robustos.
O princípio diz que: Uma classe derivada deve poder ser substituída por sua classe base sem que o comportamento do programa seja alterado ou quebrado.
Para cumprir o príncpio pense assim: “O filho (classe que herda) deve conseguir fazer tudo que o pai (classe pai) prometeu que faria”. Se o pai diz "eu sei somar dois números", o filho não pode dizer "eu só sei somar se os números forem positivos". Isso seria uma surpresa desagradável para quem usa o código.
Analogia com Pássaros
Imagine que você tem um sistema para um zoológico.
- Você cria a Entidade
Passaro - Você define que todo
Passarotem a habilidade de voar.
Até aqui, tudo bem. Aí você cria os tipos específicos:
- Pardal (É um Pássaro): Voa? Sim. ✅ (Tudo certo, segue o princípio).
- Águia (É um Pássaro): Voa? Sim. ✅ (Tudo certo).
Agora vem o problema. Você precisa adicionar um Pinguim.
- Pinguim (É um Pássaro): Voa? Não. ❌
Onde o princípio quebra?
Se você tem uma parte do seu código que diz: “Pegue todos os pássaros e faça-os voar”, quando chegar a vez do Pinguim o programa vai travar ou dar erro, porque pinguins não voam. Na aplicação promete que todo pássaro voa, mas o pinguim não cumpre essa promessa.
Como consertar o exemplo do Pinguim?
Em vez de colocar "Voar" na categoria principal Passaro, você deve criar uma categoria mais específica ou separar as habilidades:
- Classe `Passaro` (Tem bico, bota ovos).
- Classe
PassaroQueVoa(Herda de Pássaro + sabe voar). - Classe
PassaroQueNadaHerda de Pássaro + sabe nadar).
Dessa forma o Pardal entra em PassaroQueVoa e o Peguim em PassaroQueNada
E o melhor: ninguém promete o que não pode cumprir, e o código não quebra.
Exemplo aplicado ao código
Vamos retomar o exemplo acima e desenvolver com o código com C# de duas formas, uma que viola o LSP e outra que não viola.
1. Violação Estrutural
Imagine que você tem uma classe abstrata Passaro. Em C#, é comum usarmos classes abstratas ou interfaces para definir contratos.
O exemplo abaixo viola o LSP porque o Pinguim não pode substituir Passaro sem causar efeitos colaterais (a exceção).
Classe Base
public abstract class Passaro
{
public abstract void Voar();
}
Implementação Concreta
public class Pardal : Passaro
{
public override void Voar()
{
Console.WriteLine("Pardal voando alto!");
}
}
Implementação que viola o LSP
public class Pinguim : Passaro
{
public override void Voar()
{
// O compilador obriga a implementar Voar(),
// mas a lógica não permite. O dev lança uma exceção.
throw new NotImplementedException("Pinguins não voam!");
}
}
2. O Jeito Correto
Em C#, a melhor forma de resolver isso é segregando as capacidades através de Interfaces. Nem todo pássaro é um IVoador.
Essa implementação abaixo torna impossível passar um Pinguim para um método que espera IVoador. O compilador do C# vai te impedir antes mesmo de você rodar o programa.
Definição de abstração (interface e classe abstrata)
// Define apenas o que é comum a TODOS
public abstract class Ave
{
public void Comer() { Console.WriteLine("Comendo..."); }
}
// Interface (Capacidade) separada
public interface IVoador
{
void Voar();
}
Implementações
// Pardal é uma Ave E sabe voar
public class Pardal : Ave, IVoador
{
public void Voar()
{
Console.WriteLine("Pardal voando!");
}
}
// Pinguim é apenas uma Ave (não implementa IVoador)
public class Pinguim : Ave
{
// Pode ter métodos de nadar aqui
}
Top comments (0)