Basicamente, queremos que as coisas se comportem de maneira sensata.
Considere o seguinte problema:
Recebi um grupo de retângulos e quero aumentar a área deles em 10%. Então, o que 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 realmente me passou uma mistura de quadrados e retângulos, e quando o comprimento do retângulo foi alterado, o mesmo ocorreu com a largura.
Meus testes de unidade são aprovados porque escrevi todos os meus testes de unidade para usar uma coleção de retângulos. 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 outro código que usa o fato de que, se ele passar quadrados ao meu método, ele obtém um aumento de tamanho de 21% muito bom. Jim está feliz e ninguém é mais sábio.
Jim é promovido por um excelente trabalho a uma divisão diferente. Alfred se junta à empresa como um 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 deseja que o bug seja corrigido. Alfred vê que Squares e retângulos são usados em todo lugar no código e percebe que é impossível quebrar a cadeia de herança. Ele também não tem acesso ao código-fonte da Contabilidade. Então, Alfred corrige o erro da seguinte maneira:
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 hackers e Jill assina que o bug foi corrigido.
No próximo mês, ninguém é pago porque a Contabilidade dependia de poder passar quadrados para o IncreaseRectangleSizeByTenPercent
método e obter um aumento na área de 21%. A empresa inteira entra no modo "correção de bug de prioridade 1" para rastrear a origem do problema. Eles rastreiam o problema até a correção de Alfred. Eles sabem que precisam manter a contabilidade e a publicidade felizes. Portanto, eles corrigem o problema identificando o usuário com a chamada de método da seguinte maneira:
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.
Essa anedota é baseada em situações do mundo real que enfrentam os programadores diariamente. Violações do princípio da substituição de Liskov podem introduzir bugs muito sutis que só são detectados anos depois de serem escritos, quando a correção da violação quebrará um monte de coisas e não a fixação , irritará o seu maior cliente.
Existem duas maneiras realistas de corrigir esse problema.
A primeira maneira é tornar o retângulo imutável. Se o usuário do retângulo não puder alterar as propriedades Comprimento e Largura, esse problema desaparecerá. Se você deseja um retângulo com comprimento e largura diferentes, crie um novo. Quadrados podem herdar de retângulos felizmente.
A segunda maneira é quebrar a cadeia de herança entre quadrados e retângulos. Se um quadrado é definido como tendo uma única SideLength
propriedade e retângulos ter um Length
e Width
propriedade e não há herança, é impossível quebrar acidentalmente coisas, esperando que um retângulo e obter um quadrado. Em termos de C #, você pode classificar seal
sua classe de retângulo, o que garante que todos os retângulos que você obtém sejam retângulos.
Nesse caso, gosto da maneira "objetos imutáveis" de corrigir o problema. A identidade de um retângulo é seu comprimento e largura. Faz sentido que, quando você deseja alterar a identidade de um objeto, o que você realmente deseja é um novo objeto. Se você perde um cliente antigo e ganha um novo cliente, não altera o Customer.Id
campo do cliente antigo para o novo, cria um novo Customer
.
Violações do princípio da substituição de Liskov são comuns no mundo real, principalmente porque muitos códigos lá fora são escritos por pessoas que são incompetentes / sob pressão do tempo / não se importam / cometem erros. Pode e leva a alguns problemas muito desagradáveis. Na maioria dos casos, você deseja favorecer a composição sobre a herança .