Je suivais cela question très votée sur une possible violation du principe de substitution de Liskov. Je sais ce que le principe de substitution de Liskov est, mais ce qui n'est toujours pas clair dans mon esprit est ce qui pourrait se tromper si je ne pensais pas au principe tout en écrivant un code orienté objet.
Je pense que cela est très bien indiqué dans cette question qui est l'une des raisons qui ont été votées si fortement.
Maintenant, lorsque vous appelez Fermer () sur une tâche, il y a une chance que l'appel échoue s'il s'agit d'une solution de projet avec le statut démarré, quand ce ne serait pas si c'était une tâche de base.
Imaginez si vous voulez:
public void ProcessTaskAndClose(Task taskToProcess)
{
taskToProcess.Execute();
taskToProcess.DateProcessed = DateTime.Now;
taskToProcess.Close();
}
Dans cette méthode, l'appel en .Close () explosifly explosera, alors maintenant sur la base de la mise en œuvre concrète d'un type dérivé, vous devez modifier la manière dont cette méthode se comporte de la manière dont cette méthode serait écrite si la tâche 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. Ce code couplé étroitement et simplifie généralement la mise en œuvre plus difficile à utiliser de manière cohérente.
Si vous ne remplissez pas le contrat qui a été défini dans la classe de base, les choses peuvent échouer silencieusement lorsque vous obtenez des résultats éteints.
LSP dans les états de Wikipedia
Si ces personnes ne sont pas tenues, l'appelant pourrait obtenir un résultat qu'il ne s'attend pas.
dans les mots de layman :
Votre code aura une énormément de clauses de cas/commutateur partout.
Chacun de ces cas/clauses de commutation 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 travailler 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 perd la fonctionnalité souhaitée.
Exemple: Vous travaillez avec un cadre UI et vous créez votre propre contrôle ui personnalisé en sous-classant la classe de base Control
. La classe de base Control
définit une méthode getSubControls()
qui devrait renvoyer une collection de commandes imbriquées (le cas échéant). Mais vous remplacez la méthode pour renvoyer une liste des dates de naissance des présidents des États-Unis.
Alors, qu'est-ce qui peut aller mal 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. Très probablement, l'interface utilisateur va crancer. Vous êtes briser le contrat Quelles sous-classes de contrôle sont censées adhérer à.
donner un exemple de vie réel avec ndomanager de Java
il hérite de AbstractUndoableEdit
dont le contrat précise qu'il dispose de 2 états (non annulés et refaire) et peut aller entre eux avec des appels simples à undo()
et redo()
cependant, Undomerager a plus d'états et agit comme un tampon d'annulation (chaque appel à undo
annule certains mais pas tous modifier, affaiblissant la postcondition)
cela conduit à la situation hypothétique où vous ajoutez un onduleur à un composéEdit avant d'appeler end()
_ Appelez ensuite UNDO sur ce composéEditDIl conduire à appeler undo()
sur chaque modification.
J'ai roulé mon propre UndoManager
_ _ pour éviter cela (je devrais probablement le renommer à UndoBuffer
si)
J'ai hérité d'une base de code récemment qui a des violateurs majeurs de Liskov. Dans des classes importantes. Cela m'a fait d'énormes quantités de douleur. Permettez-moi d'expliquer pourquoi.
J'ai Class A
, qui dérive de Class B
. Class A
et Class B
Partager un tas de propriétés que Class A
remplace avec sa propre mise en œuvre. Fixer ou obtenir un Class A
La propriété a un effet différent pour définir ou obtenir exactement la même propriété de 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();
}
}
}
En mettant de côté le fait qu'il s'agit d'un moyen totalement terrible de traduction dans .NET, il existe un certain nombre d'autres problèmes avec ce code.
Dans ce cas Name
est utilisé comme index et une variable de contrôle de flux dans plusieurs endroits. Les classes ci-dessus sont jumellées dans tout le codeBase de leur forme brute et dérivée. La violation du principe de substitution de Liskov dans ce cas signifie que je dois connaître le contexte de chaque appel à chacune des fonctions qui prennent la classe de base.
Le code utilise des objets de la fois Class A
et Class B
, donc je ne peux pas simplement faire Class A
Résumé pour forcer les gens à utiliser Class B
.
Il existe des fonctions utilitaires très utiles qui fonctionnent sur Class A
et d'autres fonctions utilitaires très utiles qui fonctionnent sur Class B
. Idéalement, je voudrais pouvoir utiliser toute fonction d'utilité pouvant fonctionner sur Class A
au Class B
. Beaucoup de fonctions qui prennent un Class B
pourrait facilement prendre un Class A
Si ce n'était pas pour la violation du LSP.
La pire chose à ce sujet est que ce cas particulier est vraiment difficile à refracteur, car toute la demande s'installe sur ces deux classes, fonctionne tous les deux classes tout le temps et se briserait de centaines de façons si je change ceci (que je vais faire De toute façon).
Ce que je vais devoir faire pour résoudre ce problème, c'est créer une propriété NameTranslated
, qui sera la Class B
Version de la propriété Name
Changez très soigneusement toutes les références à la propriété dérivée Name
pour utiliser ma nouvelle propriété NameTranslated
. Cependant, obtiendrez même l'une de ces références de mal que l'application complète pourrait faire exploser.
Étant donné que le codeBase n'a pas de test unitaire autour de lui, il est assez proche d'être le scénario le plus dangereux qu'un développeur peut faire face. Si je ne change pas la violation, je dois dépenser d'énormes quantités d'énergie mentale de suivre quel type d'objet est utilisé dans chaque méthode et si je corrige la violation, je pouvais rendre l'ensemble du produit exploser à un temps inopportun.
Vous pouvez également le regarder d'un point de vue de 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 classe A
peut également être classé comme observable Comportement d'une instance de classe B
"(Ceci n'est possible que si la classe B
est moins spécifique que la classe A
.)
Ainsi, violer le LSP signifie qu'il existe une certaine contradiction dans votre conception: vous définissez certaines catégories pour vos objets, puis vous ne les respectez pas dans votre mise en œuvre, quelque chose doit être faux.
Comme faire une boîte avec une étiquette: "Cette boîte ne contient que des boules bleues", puis jetant une balle rouge. Quelle est l'utilisation d'une telle tag si elle affiche les mauvaises informations?