Liskovs Substitutionsprinzip: Wenn für den Subtyp ein zusätzliches Verhalten implementiert ist, das im Typ nicht vorhanden ist, liegt dann eine Verletzung von LSP vor?

8

In meinem Bestreben, besseren und saubereren Code zu schreiben, lerne ich die SOLID-Prinzipien kennen. In dieser Hinsicht erweist sich LSP als wenig schwierig, es richtig zu erfassen.

Mein Zweifel ist, was ist, wenn ich einige zusätzliche Methoden in meinem Subtyp S habe, die nicht im Typ T vorhanden waren, wird dies immer eine Verletzung von LSP sein? Wenn ja, wie mache ich dann extendmeinen Unterricht?

Nehmen wir zum Beispiel an, wir haben einen BirdTyp. Und seine Untertypen sind Eagleund Humming Bird. Jetzt haben beide Untertypen ein gemeinsames Verhalten wie die Bird. Hat aber Eagleauch gutes räuberisches Verhalten (das im allgemeinen BirdTyp nicht vorhanden ist ), das ich nutzen möchte . Daher kann ich das jetzt nicht mehr tun:

Bird bird = new Eagle();

So schenkt Eaglediese zusätzlichen Verhalten brechen LSP?

Wenn ja, bedeutet das, dass ich meine Klassen nicht erweitern kann, da dies zu einer LSP-Verletzung führen würde?

class Eagle extends Bird {
   //we are extending Bird since Eagle has some extra behavior also
}

Das Erweitern von Klassen sollte nach dem Open / Closed-Prinzip erlaubt sein, oder?

Vielen Dank im Voraus für die Antwort! Wie Sie deutlich sehen können, hat mich LSP wie alles verwirrt.

Bearbeiten: Beziehen Sie sich auf diese SO-Antwort. Auch hier verstößt es , wenn Cares zusätzliches Verhalten hat ChangeGear, gegen LSP. Wie können wir also eine Klasse erweitern, ohne LSP zu verletzen?

user270386
quelle
Ich habe das durchgemacht, aber meine Frage wurde nicht beantwortet. Ich habe tatsächlich viele Antworten gelesen, aber bisher keine Hilfe.
user270386
1
Es steht genau dort in der oberen Antwort. Haben Sie es gelesen: Denken
Mücke
@gnat, Ja, das habe ich getan, aber ich bin etwas langsam :) Und ich brauche normalerweise mehr Erklärungen, als andere vielleicht benötigen. Nachdem ich die ausführliche Antwort von David Arno gelesen habe, kann ich mich jetzt auf diese Zeile beziehen.
user270386
1
@FrankHileman Viele von uns arbeiten mit Compilern und Codebasen, die nicht ideal sind. Auch wenn wir es nicht getan haben, ist es immer noch gut, wenn die Menschen auch verstehen, wie man sie respektiert.
candied_orange

Antworten:

10

Mein Zweifel ist, was ist, wenn ich einige zusätzliche Methoden in meinem Subtyp S habe, die nicht im Typ T vorhanden waren, wird dies immer eine Verletzung von LSP sein?

Sehr einfache Antwort: nein.

Der Punkt zum LSP ist, dass er ersetzt werden Ssollte T. Wenn also Teine deleteFunktion implementiert wird, Ssollte sie auch implementiert werden und beim Aufruf ein Löschen durchführen. Es steht jedoch Sfrei, zusätzliche Funktionen hinzuzufügen, die über die angebotenen Funktionen Thinausgehen. Verbraucher von a T, wenn sie gegeben Swerden, sind sich dieser zusätzlichen Funktionalität nicht bewusst, aber es ist zulässig, dass Verbraucher von Sdirekt nutzen können.

Beispiele dafür, wie gegen das Prinzip verstoßen werden kann, sind:

class T
{
    bool delete(Item x)
    {
        if (item exists)
        {
            delete it
            return true;
        }
        return false;
    }
}

class S extends T
{
    bool delete(Item x)
    {
        if (item doesn't exist)
        {
            add it
            return false;
        }
        return true;
    }
}

Etwas komplexere Antwort: Nein, solange Sie den Status oder ein anderes erwartetes Verhalten des Basistyps nicht beeinflussen.

Zum Beispiel wäre das Folgende eine Verletzung:

class Point2D
{
    private readonly double _x;
    private readonly double _y;

    public virtual double X => _x;
    public virtual double Y => _y;

    public Point2D(double x, double y) => (_x, _y) = (x, y);
}

class MyPoint2D : Point2D
{
    private double _x;
    private double _y;

    public override double X => _x;
    public override double Y => _y;

    public MyPoint2D(double x, double y) : 
        base(x, y) => (_x, _y) = (x, y);

    public void Update(double x, double y) => (_x, _y) = (x, y);
}

Der Typ Point2Dist unveränderlich; sein Zustand kann nicht geändert werden. Mit MyPoint2Dhabe ich dieses Verhalten absichtlich umgangen, um es veränderlich zu machen. Dies unterbricht die Verlaufsbeschränkung von Point2Dund ist somit eine Verletzung des LSP.

David Arno
quelle
Vielleicht wäre eine gute Ergänzung zu dieser Antwort ein Beispiel für ein Verhalten, das zu einer solchen deleteFunktion hinzugefügt werden könnte , die eine LSP-Verletzung darstellen würde?
Andy Hunt
1
@ user270386: Nein. Bei LSP geht es nicht um die Struktur der Unterklasse, sondern um das Verhalten des Unterklassencodes im Vergleich zum Verhalten der Basisklasse. Es ist nicht die zusätzliche Funktionalität an sich, die die Verlaufsbeschränkung verletzt. Vielmehr wird die Einschränkung verletzt, wenn diese neue Funktionalität etwas Unerwartetes (z. B. Verbotenes) in der Basisklasse bewirkt (und dies schließt Dinge ein, die durch die Sprache ausgedrückt werden können, sowie Dinge, die nur durch Dokumentation ausgedrückt werden können).
Filip Milovanović
1
Es kann erwähnenswert sein, dass wenn T eine strenge Schnittstelle, eine vollständig abstrakte Klasse oder sogar das Nullobjektmuster ist, S ziemlich viel über die Struktur ist. T ist Code. Es ist nicht unbedingt eine Spezifikation, ein Anforderungsdokument oder ein Product Owner. Wir kümmern uns nur um LSP-Verstöße, wenn T verwendet wird, um eine Einschränkung zu manifestieren, die immer gelten muss. Das macht nicht jede Klasse. Aber wenn Sie S anweisen, zu löschen, und S beabsichtigt, nichts zu tun, was mit dem Rest des Codes besser in Ordnung wäre. Ich kann dir nicht sagen, ob das stimmt. Es kann Ihnen nur sagen, dass Sie dies besser überprüfen sollten, bevor Sie dies tun.
candied_orange
1
(Fortsetzung) IMO, es ist die Voraussetzung für Substituierbarkeit, die all dies antreibt. Die Substituierbarkeit ergibt sich tatsächlich aus der Fähigkeit, zwei verschiedene Implementierungen (Verhaltensweisen) auf einer höheren Abstraktionsebene, die durch den durch den Supertyp definierten Vertrag vorgeschrieben ist, als gleichwertig zu behandeln. Struktur ist in diesem Sinne ein Mittel zum Zweck.
Filip Milovanović
1
@DavidArno "der Code des Basistyps" - ist weder eine Spezifikation noch notwendigerweise zugänglich.
Frank Hileman
3

Natürlich nicht. Wenn das Eagle-Objekt von einem Code verwendet werden kann, der einen Vogel oder eine Unterklasse erwartet und sich so verhält, wie sich ein Vogel verhalten sollte, ist alles in Ordnung.

Natürlich kann das Eagle-Verhalten nur von Code verwendet werden, der weiß, dass es sich um ein solches Objekt handelt. Wir würden erwarten, dass ein Code explizit ein Eagle-Objekt erstellt und es als Eagle-Objekt verwendet, während jeder Code verwendet werden kann, der Bird-Objekte erwartet.

gnasher729
quelle