Je suivais cette question très votée sur une possible violation du principe de substitution de Liskov. Je sais ce qu'est le principe de substitution de Liskov, mais ce qui n'est pas encore clair dans mon esprit, c'est ce qui pourrait mal tourner si, en tant que développeur, je ne pense pas au principe en écrivant du code orienté objet.
27
Réponses:
Je pense que c'est très bien indiqué dans cette question, qui est l'une des raisons qui ont été si bien votées.
Imaginez si vous voulez:
Dans cette méthode, parfois l'appel .Close () explose, donc maintenant, basé sur l'implémentation concrète d'un type dérivé, vous devez changer le comportement de cette méthode par rapport à la façon dont cette méthode serait écrite si Task n'avait pas de sous-types qui pourraient être remis à cette méthode.
En raison des violations de substitution de liskov, le code qui utilise votre type devra avoir une connaissance explicite du fonctionnement interne des types dérivés pour les traiter différemment. Cela couple étroitement le code et rend généralement l'implémentation plus difficile à utiliser de manière cohérente.
la source
Si vous ne remplissez pas le contrat qui a été défini dans la classe de base, les choses peuvent échouer en silence lorsque vous obtenez des résultats erronés.
LSP dans les états de wikipedia
Si l'un de ces éléments ne tient pas, l'appelant peut obtenir un résultat auquel il ne s'attend pas.
la source
Prenons un cas classique des annales des questions d'entrevue: vous avez dérivé Circle d'Ellipse. Pourquoi? Parce qu'une ellipse de cercle IS-AN, bien sûr!
Sauf que ... l'ellipse a deux fonctions:
De toute évidence, ceux-ci doivent être redéfinis pour Circle, car un cercle a un rayon uniforme. Vous avez deux possibilités:
La plupart des langues OO ne prennent pas en charge la seconde, et pour une bonne raison: il serait surprenant de constater que votre cercle n'est plus un cercle. La première option est donc la meilleure. Mais considérez la fonction suivante:
Imaginez que some_function appelle e.set_alpha_radius. Mais comme e était vraiment un cercle, son rayon bêta est également étonnamment défini.
Et c'est là que réside le principe de substitution: une sous-classe doit être substituable à une superclasse. Sinon, des choses surprenantes se produisent.
la source
Dans les mots du profane:
Votre code aura énormément de clauses CASE / switch partout.
Chacune de ces clauses CASE / switch aura besoin de nouveaux cas ajoutés de temps en temps, ce qui signifie que la base de code n'est pas aussi évolutive et maintenable qu'elle devrait l'être.
LSP permet au code de fonctionner plus comme du matériel:
Vous n'avez pas à modifier votre iPod car vous avez acheté une nouvelle paire de haut-parleurs externes, car les anciens et les nouveaux haut-parleurs externes respectent la même interface, ils sont interchangeables sans que l'iPod ne perde la fonctionnalité souhaitée.
la source
typeof(someObject)
pour décider de ce que vous êtes "autorisé à faire", alors bien sûr, mais c'est un autre anti-modèle entièrement.pour donner un exemple réel avec UndoManager de java
il hérite du
AbstractUndoableEdit
contrat dont il spécifie qu'il a 2 états (annulé et refait) et peut passer de l'un à l'autre avec des appels uniques versundo()
etredo()
cependant UndoManager a plus d'états et agit comme un tampon d'annulation (chaque appel pour annuler
undo
certaines mais pas toutes les modifications, affaiblissant la postcondition)cela conduit à la situation hypothétique où vous ajoutez un UndoManager à un CompoundEdit avant d'appeler
end()
puis d'appeler annuler sur ce CompoundEdit le mènera à appelerundo()
à chaque modification une fois vos modifications partiellement annulées, laissantJ'ai roulé le mien
UndoManager
pour éviter cela (je devrais probablement le renommerUndoBuffer
cependant)la source
Exemple: vous travaillez avec une infrastructure d'interface utilisateur et vous créez votre propre contrôle d'interface utilisateur personnalisé en sous-classant la
Control
classe de base. LaControl
classe de base définit une méthodegetSubControls()
qui doit renvoyer une collection de contrôles imbriqués (le cas échéant). Mais vous remplacez la méthode pour renvoyer réellement une liste des dates de naissance des présidents des États-Unis.Alors, qu'est-ce qui peut mal tourner avec ça? Il est évident que le rendu du contrôle échouera, car vous ne renvoyez pas une liste de contrôles comme prévu. L'interface utilisateur se bloquera très probablement. Vous rompez le contrat auquel les sous-classes de contrôle doivent adhérer.
la source
Vous pouvez également le regarder du point de vue de la modélisation. Lorsque vous dites qu'une instance de classe
A
est également une instance de classe,B
vous impliquez que "le comportement observable d'une instance de classeA
peut également être classé comme comportement observable d'une instance de classeB
" (ceci n'est possible que si la classeB
est moins spécifique que classeA
.)Donc, violer le LSP signifie qu'il y a une contradiction dans votre conception: vous définissez des catégories pour vos objets et ensuite vous ne les respectez pas dans votre implémentation, quelque chose doit être faux.
Comme faire une boîte avec une étiquette: "Cette boîte ne contient que des boules bleues", puis y jeter une boule rouge. À quoi sert une telle étiquette si elle affiche des informations erronées?
la source
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 deClass B
.Class A
etClass B
partager un tas de propriétés quiClass A
remplace avec sa propre implémentation. La définition ou l'obtention d'uneClass A
propriété a un effet différent de la définition ou de l'obtention de la même propriété exacteClass B
.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
etClass B
, donc je ne peux pas simplement faire unClass A
résumé pour forcer les gens à l'utiliserClass B
.Il existe des fonctions utilitaires très utiles qui fonctionnent
Class A
et d'autres fonctions utilitaires très utiles qui fonctionnentClass B
. Idéalement , je voudrais être en mesure d'utiliser une fonction utilitaire qui peut fonctionner surClass A
leClass B
. De nombreuses fonctions qui prennent unClass B
pourraient facilement prendre unClass 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 laClass B
version de laName
propriété et de modifier très, très soigneusement chaque référence à laName
propriété dérivée pour utiliser ma nouvelleNameTranslated
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.
la source
BaseName
etTranslatedName
pour accéder à la fois au style de classe AName
et à la signification de classe B? Ensuite, toute tentative d'accèsName
sur une variable de typeB
serait rejetée avec une erreur de compilation, vous pouvez donc vous assurer que toutes les références ont été converties dans l'un des autres formulaires.Si vous voulez ressentir le problème de la violation de LSP, pensez à ce qui se passe si vous n'avez que .dll / .jar de classe de base (pas de code source) et que vous devez créer une nouvelle classe dérivée. Vous ne pouvez jamais terminer cette tâche.
la source