58 votes

La dérivation du carré à partir du rectangle est-elle une violation du principe de substitution de Liskov ?

Je suis nouveau dans le domaine de la conception et j'apprends les principes de conception.

Il indique que la dérivation du carré à partir du rectangle est un exemple classique de violation du principe de substitution de Liskov.

Si c'est le cas, quelle devrait être la conception correcte ?

76voto

Anton Tykhyy Points 12680

La réponse dépend de la mutabilité. Si vos classes rectangle et carré sont immuables, alors Square est en réalité un sous-type de Rectangle et il est parfaitement acceptable de dériver le premier du second. Sinon, Rectangle y Square pourrait à la fois exposer un IRectangle sans mutateur, mais dériver l'un de l'autre est une erreur car aucun des deux types n'est correctement un sous-type de l'autre.

60voto

Matt Hamilton Points 98268

Je crois que le raisonnement est à peu près le suivant :

Disons que vous avez une méthode qui accepte un rectangle et ajuste sa largeur :

public void SetWidth(Rectangle rect, int width)
{
    rect.Width = width;
}

Il devrait être parfaitement raisonnable, compte tenu de ce qu'est un rectangle, de supposer que ce test sera réussi :

Rectangle rect = new Rectangle(50, 20); // width, height

SetWidth(rect, 100);

Assert.AreEqual(20, rect.Height);

... car la modification de la largeur d'un rectangle n'affecte pas sa hauteur.

Cependant, disons que vous avez dérivé une nouvelle classe Carré de Rectangle. Par définition, un carré a une hauteur et une largeur toujours égales. Essayons à nouveau ce test :

Rectangle rect = new Square(20); // both width and height

SetWidth(rect, 100);

Assert.AreEqual(20, rect.Height);

Ce test échouera, car le fait de fixer la largeur d'un carré à 100 modifiera également sa hauteur.

Ainsi, le principe de substitution de Liskov est violé en dérivant le Carré du Rectangle.

La règle "is-a" a du sens dans le "monde réel" (un carré est bien une sorte de rectangle), mais pas toujours dans le monde de la conception de logiciels.

Modifier

Pour répondre à votre question, la conception correcte devrait probablement être que le rectangle et le carré dérivent tous deux d'une classe commune "Polygone" ou "Forme", qui n'applique aucune règle concernant la largeur ou la hauteur.

5voto

Hans Malherbe Points 1426

Je ne suis pas d'accord sur le fait que dériver un carré d'un rectangle viole nécessairement la LSP.

Dans l'exemple de Matt, si vous avez un code qui repose sur le fait que la largeur et la hauteur sont indépendantes, alors il viole effectivement la LSP.

Si toutefois, vous pouvez substituer un rectangle à un carré partout dans votre code sans briser aucune hypothèse, alors vous ne violez pas la LSP.

Donc, on en revient vraiment à ce que le rectangle d'abstraction signifie dans votre solution.

2voto

VolkerK Points 54118

Supposons que nous ayons la classe Rectangle avec les deux propriétés (publiques pour simplifier) largeur et hauteur. Nous pouvons modifier ces deux propriétés : r.width=1, r.height=2.
Maintenant, nous disons qu'un carré est un rectangle. Mais bien que l'affirmation soit "un carré se comportera comme un rectangle", nous ne pouvons pas définir .width=1 et .height=2 sur un objet carré (votre classe ajuste probablement la largeur si vous définissez la hauteur et vice versa). Il y a donc au moins un cas où un objet de type Carré ne se comporte pas comme un Rectangle et où vous ne pouvez donc pas les substituer (complètement).

1voto

user338841 Points 16

Je crois que les techniques OOD/OOP existent pour permettre aux logiciels de représenter le monde réel. Dans le monde réel, un carré est un rectangle qui a des côtés égaux. Le carré est un carré uniquement parce qu'il a des côtés égaux, et non parce qu'il a décidé d'être un carré. Par conséquent, le programme OO doit s'en occuper. Bien sûr, si la routine qui instancie l'objet veut qu'il soit carré, elle peut spécifier que la propriété longueur et la propriété largeur sont égales. Si le programme utilisant l'objet a besoin de savoir ultérieurement s'il est carré, il lui suffit de le demander. L'objet pourrait avoir une propriété booléenne en lecture seule appelée "Carré". Lorsque la routine appelante l'invoque, l'objet peut renvoyer (Longueur = Largeur). Maintenant, cela peut être le cas même si l'objet rectangle est immuable. En outre, si le rectangle est effectivement immuable, la valeur de la propriété "Square" peut être définie dans le constructeur et le tour est joué. Alors pourquoi ce problème ? La LSP exige que les sous-objets soient immuables pour s'appliquer et le carré étant un sous-objet d'un rectangle est souvent utilisé comme un exemple de sa violation. Mais cela ne semble pas être une bonne conception car lorsque la routine d'utilisation invoque l'objet comme "objSquare", elle doit connaître ses détails internes. Ne serait-il pas préférable qu'elle ne se soucie pas de savoir si le rectangle est carré ou non ? Et ce, parce que les méthodes du rectangle seraient correctes quoi qu'il en soit. Existe-t-il un meilleur exemple de violation de la PSL ?

Une autre question : comment rendre un objet immuable ? Existe-t-il une propriété "Immuable" qui peut être définie à l'instanciation ?

J'ai trouvé la réponse et c'est ce que j'attendais. Comme je suis un développeur VB .NET, c'est ce qui m'intéresse. Mais les concepts sont les mêmes dans tous les langages. En VB .NET, vous créez des classes immuables en rendant les propriétés en lecture seule et vous utilisez le constructeur New pour permettre à la routine d'instanciation de spécifier les valeurs des propriétés lors de la création de l'objet. Vous pouvez également utiliser des constantes pour certaines des propriétés et elles seront toujours les mêmes. À partir de sa création, l'objet est immuable.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X