Fondamentalement, nous voulons que les choses se comportent de manière raisonnable.
Considérez le problème suivant:
On me donne un groupe de rectangles et je veux augmenter leur surface de 10%. Donc, ce que je fais est que je règle la longueur du rectangle à 1,1 fois ce qu'il était auparavant.
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
rectangle.Length = rectangle.Length * 1.1;
}
}
Maintenant, dans ce cas, la longueur de tous mes rectangles a été augmentée de 10%, ce qui augmentera leur surface de 10%. Malheureusement, quelqu'un m'a passé un mélange de carrés et de rectangles, et lorsque la longueur du rectangle a été modifiée, la largeur l'a été également.
Mes tests unitaires réussissent parce que j'ai écrit tous mes tests unitaires pour utiliser une collection de rectangles. J'ai maintenant introduit un bogue subtil dans mon application qui peut passer inaperçu pendant des mois.
Pire encore, Jim de la comptabilité voit ma méthode et écrit un autre code qui utilise le fait que s’il passe des carrés à ma méthode, il obtient une très belle augmentation de 21% en taille. Jim est heureux et personne n'est plus sage.
Jim est promu pour son excellent travail dans une division différente. Alfred rejoint la société en tant que junior. Dans son premier rapport de bogue, Jill de Advertising a indiqué que le fait de passer des carrés à cette méthode entraîne une augmentation de 21% et veut que le bogue soit corrigé. Alfred voit que les carrés et les rectangles sont utilisés partout dans le code et se rend compte que casser la chaîne de l'héritage est impossible. De plus, il n'a pas accès au code source de la comptabilité. Alors Alfred corrige le bogue comme ceci:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle)
{
rectangle.Length = rectangle.Length * 1.1;
}
if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Alfred est satisfait de ses compétences en cyber-piratage et Jill signale que le bogue est corrigé.
Le mois prochain, personne ne sera payé car la comptabilité dépendait de la possibilité de passer des carrés à la IncreaseRectangleSizeByTenPercent
méthode et d'obtenir une augmentation de surface de 21%. Toute la société passe en mode "priorité 1 bugfix" pour retrouver la source du problème. Ils attribuent le problème à la solution d'Alfred. Ils savent qu'ils doivent garder la comptabilité et la publicité heureuses. Donc, ils résolvent le problème en identifiant l'utilisateur avec l'appel de méthode comme suit:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
IncreaseRectangleSizeByTenPercent(
rectangles,
new User() { Department = Department.Accounting });
}
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
{
rectangle.Length = rectangle.Length * 1.1;
}
else if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Et ainsi de suite.
Cette anecdote est basée sur des situations réelles auxquelles sont confrontés quotidiennement les programmeurs. Les violations du principe de substitution de Liskov peuvent introduire des bogues très subtils qui ne sont détectés que des années après leur rédaction. À ce moment-là, la résolution de la violation annulera un tas de choses et le fait de ne pas le réparer irritera votre plus gros client.
Il existe deux moyens réalistes de résoudre ce problème.
La première consiste à rendre Rectangle immuable. Si l'utilisateur de Rectangle ne peut pas modifier les propriétés Longueur et Largeur, ce problème disparaît. Si vous souhaitez un rectangle de longueur et de largeur différentes, vous en créez un nouveau. Les carrés peuvent hériter des rectangles avec bonheur.
La deuxième méthode consiste à rompre la chaîne d'héritage entre carrés et rectangles. Si un carré est défini comme ayant une seule SideLength
propriété et rectangles ont une Length
et Width
propriété et il n'y a pas d' héritage, il est impossible de briser accidentellement les choses en attendant un rectangle et d' obtenir un carré. En termes C #, vous pouvez seal
utiliser votre classe de rectangle, ce qui garantit que tous les rectangles que vous obtenez sont réellement des rectangles.
Dans ce cas, j'aime bien la manière de "résoudre les problèmes avec des" objets immuables ". L'identité d'un rectangle est sa longueur et sa largeur. Il est logique que lorsque vous souhaitez modifier l'identité d'un objet, ce que vous voulez vraiment, c'est un nouvel objet. Si vous perdez un ancien client et gagnez un nouveau client, vous ne modifiez pas le Customer.Id
champ de l'ancien client au nouveau, vous créez un nouveau Customer
.
Les violations du principe de substitution de Liskov sont courantes dans le monde réel, principalement parce que de nombreux codes sont écrits par des personnes incompétentes / soumises à des contraintes de temps / qui ne s'en soucient pas / qui font des erreurs. Cela peut conduire à de très vilains problèmes. Dans la plupart des cas, vous préférez la composition à l'héritage .