Wie verifiziere ich das Liskov-Substitutionsprinzip in einer Vererbungshierarchie?

14

Inspiriert von dieser Antwort:

Liskov Substitutionsprinzip erfordert , dass

  • Voraussetzungen können in einem Subtyp nicht gestärkt werden.
  • Nachbedingungen können in einem Subtyp nicht abgeschwächt werden.
  • Invarianten des Supertyps müssen in einem Subtyp erhalten bleiben.
  • Verlaufsbeschränkung (die "Verlaufsregel"). Objekte gelten nur durch ihre Methoden als veränderbar (Kapselung). Da Subtypen Methoden einführen können, die im Supertyp nicht vorhanden sind, kann die Einführung dieser Methoden Zustandsänderungen im Subtyp ermöglichen, die im Supertyp nicht zulässig sind. Die Geschichtsbedingung verbietet dies.

Ich hatte gehofft, jemand würde eine Klassenhierarchie posten, die diese 4 Punkte verletzt, und wie man sie entsprechend löst.
Ich bin auf der Suche nach einer ausführlichen Erklärung für Bildungszwecke, wie jeder der vier Punkte in der Hierarchie identifiziert und am besten behoben werden kann.

Hinweis:
Ich hatte gehofft, ein Codebeispiel zu veröffentlichen, an dem die Leute arbeiten können, aber die Frage selbst ist, wie die fehlerhaften Hierarchien identifiziert werden können :)

Songo
quelle
In den Antworten auf diese SO-Frage sind
StuartLC

Antworten:

17

Es ist viel einfacher als dieses Zitat es klingt, genau wie es ist.

Stellen Sie sich bei einer Vererbungshierarchie eine Methode vor, die ein Objekt der Basisklasse empfängt. Stellen Sie sich nun die Frage, ob möglicherweise Annahmen vorliegen, die eine Person, die diese Methode bearbeitet, für diese Klasse ungültig macht.

Zum Beispiel ( ursprünglich auf Onkel Bobs Website zu sehen ):

public class Square : Rectangle
{
    public Square(double width) : base(width, width)
    {
    }

    public override double Width
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Width;
        }
    }

    public override double Height
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Height;
        }
    }
}

Scheint fair genug, oder? Ich habe eine spezielle Art von Rechteck namens Quadrat erstellt, bei der die Breite immer gleich der Höhe sein muss. Ein Quadrat ist ein Rechteck, also passt es zu den OO-Prinzipien, nicht wahr?

Aber was ist, wenn jetzt jemand diese Methode schreibt:

public void Enlarge(Rectangle rect, double factor)
{
    rect.Width *= factor;
    rect.Height *= factor;
}

Nicht cool. Es gibt jedoch keinen Grund, warum der Autor dieser Methode hätte wissen müssen, dass ein potenzielles Problem vorliegen könnte.

Denken Sie jedes Mal, wenn Sie eine Klasse von einer anderen ableiten, über die Basisklasse nach und darüber, was die Leute davon annehmen könnten (z. B. "Sie hat eine Breite und eine Höhe und beide wären unabhängig"). Dann überlegen Sie, ob diese Annahmen in meiner Unterklasse gültig bleiben. Wenn nicht, überdenken Sie Ihr Design.

pdr
quelle
Sehr gutes und subtiles Beispiel. +1. Sie können eine Methode der Rectangle-Klasse vergrößern und in der Square-Klasse überschreiben.
Marco-Fiset
@ marco-fiset: Ich würde eher Square und Rectangle entkoppelt sehen, Square mit nur einer Dimension, aber jeder implementiert IResizable. Wenn es eine Draw-Methode gäbe, wären sie zwar ähnlich, aber dann sollten beide eine RectangleDrawer-Klasse einschließen, die den allgemeinen Code enthält.
pdr
1
Ich denke nicht, dass dies ein gutes Beispiel ist. Das Problem ist, dass ein Quadrat keine Breite oder Höhe hat. Es hat nur eine Länge von seinen Seiten. Das Problem wäre nicht da, wenn Breite und Höhe nur lesbar wären, aber in diesem Fall sind sie beschreibbar. Wenn ein modifizierbarer Zustand eingeführt wird, ist es immer schwieriger, den LSP aufrechtzuerhalten.
SpaceTrucker
@pdr Danke für das Beispiel, aber in Bezug auf die 4 Bedingungen, die ich in meinem Beitrag erwähnt habe, Squareverletzt welcher Teil der Klasse sie?
Songo
1
@Songo: Es ist die Geschichtsbedingung. Besser hier erklärt: blackwasp.co.uk/LSP.aspx "Von Natur aus enthalten Unterklassen alle Methoden und Eigenschaften ihrer Superklassen. Sie können auch weitere Mitglieder hinzufügen. Die Verlaufsbeschränkung besagt, dass neue oder geänderte Mitglieder die nicht ändern sollten Zustand eines Objekts auf eine Weise, die die Basisklasse nicht zulässt. Wenn die Basisklasse beispielsweise ein Objekt mit einer festen Größe darstellt, sollte die Unterklasse nicht zulassen, dass diese Größe geändert wird. "
pdr