Recientemente heredé una base de código que tiene algunos infractores importantes de Liskov. En clases importantes. Esto me ha causado enormes cantidades de dolor. Déjame explicarte por qué.
Tengo Class A
, que se deriva de Class B
. Class A
y Class B
comparte un montón de propiedades que Class A
anula con su propia implementación. Establecer u obtener una Class A
propiedad tiene un efecto diferente al establecer u obtener exactamente la misma propiedad 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();
}
}
}
Dejando a un lado el hecho de que esta es una forma absolutamente terrible de hacer la traducción en .NET, hay otros problemas con este código.
En este caso Name
se usa como índice y variable de control de flujo en varios lugares. Las clases anteriores están plagadas en toda la base de código, tanto en su forma sin procesar como derivada. Violar el principio de sustitución de Liskov en este caso significa que necesito saber el contexto de cada llamada a cada una de las funciones que toman la clase base.
El código usa objetos de ambos Class A
y Class B
, por lo tanto, no puedo simplemente hacer un Class A
resumen para obligar a las personas a usar Class B
.
Hay algunas funciones de utilidad muy útiles que funcionan Class A
y otras funciones de utilidad muy útiles que funcionan Class B
. Idealmente me gustaría ser capaz de utilizar cualquier función de utilidad que puede operar en Class A
el Class B
. Muchas de las funciones que toman una Class B
podrían tomar fácilmente una, Class A
si no fuera por la violación del LSP.
Lo peor de esto es que este caso particular es realmente difícil de refactorizar ya que la aplicación completa depende de estas dos clases, opera en ambas clases todo el tiempo y se rompería de cien maneras si cambio esto (lo que voy a hacer de todas formas).
Lo que tendré que hacer para solucionar esto es crear una NameTranslated
propiedad, que será la Class B
versión de la Name
propiedad y cambiar muy cuidadosamente todas las referencias a la Name
propiedad derivada para usar mi nueva NameTranslated
propiedad. Sin embargo, si una de estas referencias se equivoca, toda la aplicación podría explotar.
Dado que el código base no tiene pruebas unitarias a su alrededor, esto está bastante cerca de ser el escenario más peligroso que un desarrollador puede enfrentar. Si no cambio la infracción, tengo que gastar enormes cantidades de energía mental para realizar un seguimiento de qué tipo de objeto se está utilizando en cada método y si soluciono la infracción, podría hacer que todo el producto explote en un momento inoportuno.