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 A
e Class B
compartilhe várias propriedades que Class A
substituem sua própria implementação. Definir ou obter uma Class A
propriedade 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 A
e Class B
, portanto, não posso simplesmente tornar Class A
abstrato para forçar as pessoas a usar Class B
.
Existem algumas funções utilitárias muito úteis que operam Class A
e 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 A
on Class B
. Muitas das funções que aceitam um Class B
poderiam facilmente aceitar um, Class A
se 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 NameTranslated
propriedade, que será a Class B
versão da Name
propriedade e muito, muito cuidadosamente, alterar todas as referências à Name
propriedade derivada para usar minha nova NameTranslated
propriedade. 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.