Stack erweitert LinkedList. Ein Verstoß gegen das Liskov-Substitutionsprinzip?

13

Es gibt eine Klasse LinkedList mit Funktionen wie add_first (), add_last (), add_after (), remove_first (), remove_last () und remove ().

Jetzt gibt es einen Klassenstapel, der Funktionen wie push (), pop (), peek () oder top () bereitstellt. Um diese Methoden zu implementieren, werden die LinkedList-Klassenmethoden erweitert. Handelt es sich um eine Verletzung des Liskov-Substitutionsprinzips?

Betrachten Sie beispielsweise den Fall add_after (), um einen Knoten an der n-ten Position einer verknüpften Liste hinzuzufügen. Dies kann in der Basisklasse erfolgen, jedoch nicht in der Stack-Klasse. Werden hier Nachbedingungen abgeschwächt oder ändern Sie die Methode add_after (), um sie an die Spitze des Stapels zu setzen?

Auch wenn nicht eine Verletzung, ist das schlechtes Design? Und wie würden Sie die Stack-Funktionalität mithilfe der LinkedList-Klasse implementieren?

jxvmiranda
quelle
6
Die Frage Was wäre der Nachteil, eine Klasse als Unterklasse einer Liste von sich selbst zu definieren? fragt nach einem etwas anderen Problem, aber die meisten Antworten gelten auch hier: Es ist besser, einen Stack mit einer Liste als privatem Mitglied zu erstellen, als von List zu erben.
amon
7
Ja, dies ist ein schlechtes Design, da Sie dadurch nicht die interne Darstellung des Stapels in eine andere als eine verknüpfte Liste (z. B. ein Array) ändern können. Sie legen auch Vorgänge offen, die Stapel normalerweise nicht unterstützen. Verwenden Sie Komposition anstelle von Vererbung.
Lee
2
Möchten Sie verlängern LinkedList? Vielleicht möchten Sie den Rat eines bestimmten Joshua Bloch (der eine wichtige Rolle beim Entwerfen des Java-Kollektions-Frameworks spielte) befolgen, als er sagte: "Komposition vor Vererbung" - Haben LinkedListSie vielleicht eine in Ihrer Stapelklasse, aber erweitern Sie sie nicht wirklich?
CorsiKa
1
Ich würde sagen, es kann das Liskov-Substitutionsprinzip nicht erfüllen, da "ein Stapel eine Art verknüpfte Liste ist" keine wahre Aussage ist. "Stack" ist wirklich eine Schnittstelle (ich meine konzeptionell nicht das Java-Konstrukt). Ein Stack kann mit einer verknüpften Liste implementiert werden . Wie andere bereits gesagt haben, würden Sie ein privates Datenelement des Typs verwenden LinkedList, das die Arbeit hinter den Kulissen erledigt (Komposition). Auf diese Weise können Benutzer Ihres Codes nicht versehentlich einen Stapel verwenden, für den sie eine Liste benötigten, oder umgekehrt.
Jasmijn
1
Es scheint, was vernünftiger wäre, wäre das Gegenteil. Wenn ich dies tun würde, würde ich wahrscheinlich eine verknüpfte Listenklasse haben, die eine "Stapel" -Schnittstelle implementiert, da sie dazu vollständig in der Lage ist. Andernfalls ist dies falsch herum, da ein Stapel ein Konzept ist, eine verknüpfte Liste eine Implementierung ist, die unter anderem als Stapel fungieren kann.
Vality

Antworten:

31

Jetzt gibt es einen Klassenstapel, der Funktionen wie push (), pop (), peek () oder top () bereitstellt. Um diese Methoden zu implementieren, werden die LinkedList-Klassenmethoden erweitert. Handelt es sich um eine Verletzung des Liskov-Substitutionsprinzips?

Nein. Es ist vollkommen in Ordnung, Methoden in einem Subtyp hinzuzufügen.

Betrachten Sie beispielsweise den Fall add_after (), um einen Knoten an der n-ten Position einer verknüpften Liste hinzuzufügen. Dies kann in der Basisklasse erfolgen, jedoch nicht in der Stack-Klasse.

Dies ist eine Verletzung des LSP. Der LSP sagt, dass Instanzen eines Subtyps durch Instanzen eines Supertyps ersetzt werden müssen, ohne die gewünschten Eigenschaften eines Programms zu ändern. Wenn ein Subtyp eine Methode entfernt, stürzt jeder Code ab, der diese Methode aufruft (oder eine NoMethodErrorAusnahme oder etwas in dieser Richtung). "Nicht abstürzen" ist eindeutig eine wünschenswerte Eigenschaft.

Werden hier Nachbedingungen abgeschwächt oder ändern Sie die Methode add_after (), um sie an die Spitze des Stapels zu setzen?

Das Ändern der add_after()Methode auf diese Weise stellt einen Verstoß gegen die History-Regel (die wichtigste der Regeln!) Dar und hilft daher nicht, den Verstoß gegen den LSP zu beheben.

Und wie würden Sie die Stack-Funktionalität mithilfe der LinkedList-Klasse implementieren?

Durch die Verwendung von Komposition.

HINWEIS: Alles, was ich oben geschrieben habe , gilt nur für Sprachen, die Untertypen und Unterklassen verwechseln! Beim LSP geht es um Untertypen , nicht um Unterklassen. In einer Sprache, die die beiden nicht verwechselt, ist es durchaus akzeptabel, Stackeine Unterklasse von zu erstellen LinkedList, sofern Sie sie nicht zu einem Untertyp von machen LinkedList.

Jörg W. Mittag
quelle
9
Was ist die Geschichtsregel? Ich habe es im Internet nicht gefunden.
Zapadlo
10
Wenn Sie ein Objekt eines Subtyps manipulieren und es mit Methoden des Supertyps beobachten, darf es unmöglich sein, eine Historie zu beobachten, die nicht auch mit einem Objekt des Supertyps beobachtet werden konnte. Diese Regel macht den LSP auf nicht funktionierende Sprachen anwendbar. Alle anderen Sachen waren schon lange vorher bekannt. Die Vor- und Nachbedingungsregeln sind Neuformulierungen der Co- und Kontravarianzregeln für Funktionstypen. Die Verlaufsregel macht all dies auf veränderbare Daten anwendbar. Ohne sie wäre der LSP nur für rein funktionale Sprachen nützlich.
Jörg W Mittag
2
Ich finde die Erklärung im Wikipedia-Artikel einigermaßen gut: wikipedia.org/wiki/Liskov_substitution_principle Aber wie immer sollte man wirklich die Originalquelle lesen.
Jörg W Mittag
@ JörgWMittag: Während ich es für sinnvoll halte, dass Typen in ihren Vertrag Garantien darüber aufnehmen, welche "Geschichte" beobachtet wird oder nicht, denke ich nicht, dass es notwendig sein sollte, dass jeder Supertyp in der Lage ist, jede Abfolge von Beobachtungen zu produzieren Dies könnte für ein Objekt des Subtyps möglich sein - lediglich, dass der Supertyp die Möglichkeit dokumentiert, dass Konsumenten des Supertyps solche Sequenzen beobachten könnten.
Supercat
1

Da Jörg W. Mittag bereits alles angesprochen hat, möchte ich nur auf den folgenden Teil eingehen:

Werden hier Nachbedingungen abgeschwächt oder ändern Sie die Methode add_after (), um sie an die Spitze des Stapels zu setzen?

Grundsätzlich hängt es von Ihrem Vertrag ab, ob eine Hierarchie gegen LSP verstößt. Also, welchen Vertrag hat add_afterdas? Wenn es so verrückt klingt, wie "Add a node at the n-th position oder at the top", dann ist die Post-Bedingung erfüllt, LSP wird nicht verletzt. Andernfalls liegt eine LSP-Verletzung vor.

Zapadlo
quelle