Basicamente, queremos que as coisas se comportem de maneira sensata.
Considere o seguinte problema:
Recebo um grupo de retângulos e quero aumentar sua área em 10%. Então, o que eu faço é definir o comprimento do retângulo para 1,1 vezes o que era antes.
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
rectangle.Length = rectangle.Length * 1.1;
}
}
Agora, neste caso, todos os meus retângulos agora têm seu comprimento aumentado em 10%, o que aumentará sua área em 10%. Infelizmente, alguém passou por mim uma mistura de quadrados e retângulos, e quando o comprimento do retângulo foi alterado, o mesmo aconteceu com a largura.
Meus testes de unidade são aprovados porque eu escrevi todos os meus testes de unidade para usar uma coleção de retângulos. Eu agora introduzi um bug sutil no meu aplicativo, que pode passar despercebido por meses.
Pior ainda, Jim da contabilidade vê meu método e escreve algum outro código que usa o fato de que, se ele passar quadrados para o meu método, ele obterá um aumento de 21% no tamanho. Jim é feliz e ninguém é mais sábio.Jim é promovido por excelente trabalho para uma divisão diferente. Alfred se junta à empresa como júnior. Em seu primeiro relatório de bug, Jill, da Advertising, relatou que a passagem de quadrados para esse método resulta em um aumento de 21% e quer que o bug seja corrigido. Alfred vê que Squares e Rectangles são usados em todo o código e percebe que quebrar a cadeia de herança é impossível. Ele também não tem acesso ao código-fonte da Contabilidade. Então Alfred corrige o bug assim:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle)
{
rectangle.Length = rectangle.Length * 1.1;
}
if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Alfred está feliz com suas habilidades de hacker e Jill assina que o bug foi corrigido.
No próximo mês, ninguém é pago porque a Contabilidade estava dependente de poder passar quadrados para o método IncreaseRectangleSizeByTenPercent
e obter um aumento na área de 21%. Toda a empresa entra no modo "correção de prioridade 1" para rastrear a origem do problema. Eles rastreiam o problema para a correção de Alfred. Eles sabem que precisam manter a Contabilidade e a Publicidade felizes. Então, eles corrigem o problema identificando o usuário com a chamada do método da seguinte forma:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
IncreaseRectangleSizeByTenPercent(
rectangles,
new User() { Department = Department.Accounting });
}
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
{
rectangle.Length = rectangle.Length * 1.1;
}
else if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
E assim por diante e assim por diante.
Esta anedota é baseada em situações do mundo real que enfrentam programadores diariamente. Violações do Princípio da Substituição de Liskov podem introduzir bugs muito sutis que só são captados anos depois de serem escritos, quando a correção da violação vai quebrar um monte de coisas e não consertar ele irá irritar seu maior problema cliente.
Existem duas formas realistas de corrigir este problema.
A primeira maneira é tornar o Rectangle imutável. Se o usuário do Retângulo não puder alterar as propriedades Comprimento e Largura, esse problema desaparece. Se você quiser um retângulo com comprimento e largura diferentes, crie um novo. Quadrados podem herdar de retângulos alegremente.
A segunda maneira é quebrar a cadeia de herança entre quadrados e retângulos. Se um quadrado for definido como tendo uma única propriedade SideLength
e os retângulos tiverem uma propriedade Length
e Width
e não houver herança, será impossível acidentalmente quebrar as coisas esperando um retângulo e obtendo um quadrado. Em termos de C #, você pode usar seal
de sua classe de retângulo, o que garante que todos os Retângulos que você recebe sejam na verdade Retângulos.
Nesse caso, gosto da maneira "objetos imutáveis" de consertar o problema. A identidade de um retângulo é seu comprimento e largura. Faz sentido que quando você quer mudar a identidade de um objeto, o que você realmente quer é um objeto novo . Se você perder um cliente antigo e ganhar um novo cliente, não alterará o campo Customer.Id
do cliente antigo para o novo, criará um novo Customer
.
Violações do princípio de Substituição de Liskov são comuns no mundo real, principalmente porque muito código lá fora é escrito por pessoas que são incompetentes / sob pressão de tempo / não se importam / cometem erros. Pode e leva a alguns problemas muito desagradáveis. Na maioria dos casos, você quer preferir a composição sobre a herança .