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 Aet Class Bpartager un tas de propriétés qui Class Aremplace avec sa propre implémentation. La définition ou l'obtention d'une Class Aproprié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, Nameest 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 Aet Class B, donc je ne peux pas simplement faire un Class Arésumé pour forcer les gens à l'utiliser Class B.
Il existe des fonctions utilitaires très utiles qui fonctionnent Class Aet 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 Ale Class B. De nombreuses fonctions qui prennent un Class Bpourraient facilement prendre un Class Asans 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 NameTranslatedpropriété, qui sera la Class Bversion de la Namepropriété et de modifier très, très soigneusement chaque référence à la Namepropriété dérivée pour utiliser ma nouvelle NameTranslatedproprié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.