Herdei recentemente uma base de código que contém alguns dos principais violadores de Liskov. Em aulas importantes. Isso me causou enormes quantidades de dor. Deixe-me explicar o porquê.
Eu tenho Class A, que deriva Class B. Class Ae Class Bcompartilhe várias propriedades que Class Asubstituem sua própria implementação. Definir ou obter uma Class Apropriedade tem um efeito diferente de definir ou obter exatamente a mesma propriedade Class B.
public Class A
{
public virtual string Name
{
get; set;
}
}
Class B : A
{
public override string Name
{
get
{
return TranslateName(base.Name);
}
set
{
base.Name = value;
FunctionWithSideEffects();
}
}
}
Deixando de lado o fato de que essa é uma maneira absolutamente terrível de fazer a tradução no .NET, existem vários outros problemas com esse código.
Nesse caso, Nameé usado como um índice e uma variável de controle de fluxo em vários locais. As classes acima estão espalhadas por toda a base de código, tanto na forma bruta quanto na derivada. Violar o princípio de substituição de Liskov neste caso significa que eu preciso conhecer o contexto de cada chamada para cada uma das funções que levam a classe base.
O código usa objetos de ambos Class Ae Class B, portanto, não posso simplesmente tornar Class Aabstrato para forçar as pessoas a usar Class B.
Existem algumas funções utilitárias muito úteis que operam Class Ae outras funções utilitárias muito úteis que operam Class B. Idealmente, eu gostaria de ser capaz de usar qualquer função de utilitário que pode operar em Class Aon Class B. Muitas das funções que aceitam um Class Bpoderiam facilmente aceitar um, Class Ase não fosse pela violação do LSP.
O pior de tudo é que esse caso em particular é realmente difícil de refatorar, pois todo o aplicativo depende dessas duas classes, opera nas duas classes o tempo todo e quebraria de centenas de maneiras se eu mudar isso (o que vou fazer) de qualquer forma).
O que terei que fazer para corrigir isso é criar uma NameTranslatedpropriedade, que será a Class Bversão da Namepropriedade e muito, muito cuidadosamente, alterar todas as referências à Namepropriedade derivada para usar minha nova NameTranslatedpropriedade. No entanto, ao errar uma dessas referências, o aplicativo inteiro pode explodir.
Dado que a base de código não possui testes de unidade, é quase o cenário mais perigoso que um desenvolvedor pode enfrentar. Se eu não alterar a violação, tenho que gastar uma quantidade enorme de energia mental acompanhando que tipo de objeto está sendo operado em cada método e, se eu corrigir a violação, poderia fazer o produto inteiro explodir em um momento inoportuno.