J'ai récemment hérité d'une base de code qui contient quelques violateurs majeurs de Liskov. Dans les classes importantes. Cela m'a causé énormément de douleur. Laissez-moi vous expliquer pourquoi.
J'ai Class A
, qui dérive de Class B
. Class A
et Class B
partager un tas de propriétés qui Class A
remplace avec sa propre implémentation. La définition ou l'obtention d'une Class A
propriété a un effet différent de la définition ou de l'obtention de la même propriété exacte 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();
}
}
}
Mis à part le fait qu'il s'agit d'une façon tout à fait terrible de faire la traduction dans .NET, il existe un certain nombre d'autres problèmes avec ce code.
Dans ce cas, Name
est utilisé comme index et variable de contrôle de flux à plusieurs endroits. Les classes ci-dessus sont jonchées dans toute la base de code sous leur forme brute et dérivée. Violer le principe de substitution de Liskov dans ce cas signifie que j'ai besoin de connaître le contexte de chaque appel unique à chacune des fonctions qui prennent la classe de base.
Le code utilise des objets des deux Class A
et Class B
, donc je ne peux pas simplement faire un Class A
résumé pour forcer les gens à l'utiliser Class B
.
Il existe des fonctions utilitaires très utiles qui fonctionnent Class A
et d'autres fonctions utilitaires très utiles qui fonctionnent Class B
. Idéalement , je voudrais être en mesure d'utiliser une fonction utilitaire qui peut fonctionner sur Class A
le Class B
. De nombreuses fonctions qui prennent un Class B
pourraient facilement prendre un Class A
sans la violation du LSP.
La pire chose à ce sujet est que ce cas particulier est vraiment difficile à refactoriser car l'application entière dépend de ces deux classes, fonctionne tout le temps sur les deux classes et se briserait d'une centaine de manières si je change cela (ce que je vais faire en tous cas).
Ce que je devrai faire pour résoudre ce problème est de créer une NameTranslated
propriété, qui sera la Class B
version de la Name
propriété et de modifier très, très soigneusement chaque référence à la Name
propriété dérivée pour utiliser ma nouvelle NameTranslated
propriété. Cependant, même si l'une de ces références est erronée, l'application entière pourrait exploser.
Étant donné que la base de code n'a pas de tests unitaires autour d'elle, cela est assez proche d'être le scénario le plus dangereux auquel un développeur peut être confronté. Si je ne change pas la violation, je dois dépenser d'énormes quantités d'énergie mentale pour suivre le type d'objet utilisé dans chaque méthode et si je corrige la violation, je pourrais faire exploser le produit entier à un moment inopportun.