Ist das Liskov-Substitutionsprinzip nicht mit Introspektion oder Ententypisierung vereinbar?

11

Verstehe ich richtig, dass das Liskov-Substitutionsprinzip in Sprachen, in denen sich Objekte selbst inspizieren können, nicht eingehalten werden kann, wie es in Sprachen mit Ententyp üblich ist?

Zum Beispiel in Ruby, wenn eine Klasse Berbt von einer Klasse A, dann für jedes Objekt xvon A, x.classnach Rückkehr geht A, aber wenn xein Gegenstand ist B, x.classwird nicht zurückkehren A.

Hier ist eine Aussage von LSP:

Sei q (x) eine Eigenschaft, die für Objekte x vom Typ T beweisbar ist . Dann sollte q (y) für Objekte y vom Typ S beweisbar sein, wobei S ein Subtyp von T ist .

So zum Beispiel in Ruby

class T; end
class S < T; end

LSP in dieser Form verletzen, wie die Eigenschaft q (x) = zeigtx.class.name == 'T'


Zusatz. Wenn die Antwort "Ja" lautet (LSP nicht kompatibel mit Selbstbeobachtung), dann wäre meine andere Frage: Gibt es eine modifizierte "schwache" Form von LSP, die möglicherweise für eine dynamische Sprache gelten kann, möglicherweise unter bestimmten Bedingungen und nur mit speziellen Typen von Eigenschaften .


Aktualisieren. Als Referenz ist hier eine andere Formulierung von LSP, die ich im Web gefunden habe:

Funktionen, die Zeiger oder Verweise auf Basisklassen verwenden, müssen Objekte abgeleiteter Klassen verwenden können, ohne es zu wissen.

Und ein anderer:

Wenn S ein deklarierter Subtyp von T ist, sollten sich Objekte vom Typ S so verhalten, wie sich Objekte vom Typ T voraussichtlich verhalten, wenn sie als Objekte vom Typ T behandelt werden.

Der letzte ist kommentiert mit:

Beachten Sie, dass es beim LSP ausschließlich um das erwartete Verhalten von Objekten geht. Man kann dem LSP nur folgen, wenn man sich über das erwartete Verhalten von Objekten im Klaren ist.

Dies scheint schwächer zu sein als das ursprüngliche und könnte beobachtet werden, aber ich würde es gerne formalisieren, insbesondere erklären, wer über das erwartete Verhalten entscheidet.

Ist LSP dann nicht eine Eigenschaft eines Klassenpaars in einer Programmiersprache, sondern eines Klassenpaares zusammen mit einem bestimmten Satz von Eigenschaften, die von der Vorgängerklasse erfüllt werden? Würde dies praktisch bedeuten, dass alle möglichen Verwendungen der Ahnenklasse bekannt sein müssen, um eine Unterklasse (Nachkommenklasse) zu erstellen, die LSP respektiert? Laut LSP soll die Ahnenklasse durch eine Nachkommenklasse ersetzt werden können, oder?


Aktualisieren. Ich habe die Antwort bereits akzeptiert, möchte aber noch ein konkretes Beispiel von Ruby hinzufügen, um die Frage zu veranschaulichen. In Ruby ist jede Klasse ein Modul in dem Sinne, dass die ClassKlasse ein Nachkomme der ModuleKlasse ist. Jedoch:

class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module

module M; end
M.class # => Module

o = Object.new

o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)
Alexey
quelle
2
Fast alle modernen Sprachen bieten ein gewisses Maß an Selbstbeobachtung, daher ist die Frage nicht wirklich spezifisch für Ruby.
Joachim Sauer
Ich verstehe, ich habe Ruby nur als Beispiel gegeben. Ich weiß nicht, vielleicht gibt es in einigen anderen Sprachen mit Selbstbeobachtung einige "schwache Formen" von LSP, aber wenn ich das Prinzip richtig verstanden habe, ist es nicht mit Selbstbeobachtung vereinbar.
Alexey
Ich habe "Ruby" aus dem Titel entfernt.
Alexey
2
Die kurze Antwort lautet: Sie sind kompatibel. Hier ist ein Blog-Beitrag, dem ich größtenteils zustimme: Liskov-Substitutionsprinzip für das
Entenschreiben
2
@Alexey Eigenschaften sind in diesem Zusammenhang Invarianten eines Objekts. Unveränderliche Objekte haben beispielsweise die Eigenschaft, dass sich ihre Werte nicht ändern. Wenn Sie sich gute Komponententests ansehen, sollten diese genau auf diese Eigenschaften getestet werden.
K.Steff

Antworten:

29

Hier ist das eigentliche Prinzip :

Sei q(x)eine Eigenschaft, die über Objekte xvom Typ beweisbar ist T. Dann q(y)sollte für Objekte yvom Typ beweisbar sein, bei Sdenen Ses sich um einen Subtyp von handelt T.

Und die ausgezeichnete Wikipedia- Zusammenfassung:

Es heißt, dass in einem Computerprogramm, wenn S ein Subtyp von T ist, Objekte vom Typ T durch Objekte vom Typ S ersetzt werden können (dh Objekte vom Typ S können durch Objekte vom Typ S ersetzt werden, ohne eines von ihnen zu ändern) die gewünschten Eigenschaften dieses Programms (Korrektheit, ausgeführte Aufgabe usw.).

Und einige relevante Zitate aus dem Papier:

Was benötigt wird, ist eine stärkere Anforderung, die das Verhalten von Untertypen einschränkt: Eigenschaften, die anhand der Spezifikation des vermuteten Typs eines Objekts nachgewiesen werden können, sollten gelten, obwohl das Objekt tatsächlich Mitglied eines Untertyps dieses Typs ist ...

Eine Typspezifikation enthält die folgenden Informationen:
- den Namen des Typs;
- Eine Beschreibung des Wertebereichs des Typs;
- Für jede Methode des Typs:
--- Sein Name;
--- seine Unterschrift (einschließlich signalisierter Ausnahmen);
--- Sein Verhalten in Bezug auf Vor- und Nachbedingungen.

Also weiter zur Frage:

Verstehe ich richtig, dass das Liskov-Substitutionsprinzip in Sprachen, in denen sich Objekte selbst inspizieren können, nicht eingehalten werden kann, wie es in Sprachen mit Ententyp üblich ist?

Nein.

A.classgibt eine Klasse zurück.
B.classgibt eine Klasse zurück.

Da Sie denselben Anruf für den spezifischeren Typ tätigen und ein kompatibles Ergebnis erhalten können, gilt LSP. Das Problem ist, dass Sie mit dynamischen Sprachen immer noch Dinge im Ergebnis aufrufen können, die erwarten, dass sie vorhanden sind.

Betrachten wir jedoch eine statisch strukturelle (Enten-) Sprache. In diesem Fall A.classwürde ein Typ mit einer Einschränkung zurückgegeben, die er sein muss, Aoder ein Subtyp von A. Dies bietet die statische Garantie, dass jeder Subtyp von Aeine Methode bereitstellen muss, T.classderen Ergebnis ein Typ ist, der diese Einschränkung erfüllt.

Dies liefert eine stärkere Behauptung, dass LSP in Sprachen gilt, die die Typisierung von Enten unterstützen, und dass jede Verletzung von LSP in so etwas wie Ruby eher auf normalen dynamischen Missbrauch als auf eine Inkompatibilität des Sprachdesigns zurückzuführen ist.

Telastyn
quelle
1
"Da Sie denselben Anruf für den spezifischeren Typ tätigen und ein kompatibles Ergebnis erhalten können, gilt LSP". LSP gilt, wenn die Ergebnisse identisch sind, wenn ich es richtig verstanden habe. Möglicherweise kann es eine "Wochen" -Form von LSP in Bezug auf gegebene Einschränkungen geben, die anstelle aller Eigenschaften erfordert, dass nur die gegebenen Einschränkungen erfüllt werden. Auf jeden Fall würde ich mich über jede Referenz freuen.
Alexey
@Alexey bearbeitet, um einzuschließen, was LSP bedeutet. Wenn ich B dort verwenden kann, wo ich A erwarte, gilt LSP. Ich bin gespannt, wie du denkst, dass Rubys .class das möglicherweise verletzt.
Telastyn
3
@Alexey - Wenn Ihr Programm enthält fail unless x.foo == 42und ein Subtyp 0 zurückgibt, ist das dasselbe. Dies ist kein Fehler von LSP, sondern der normale Betrieb Ihres Programms. Polymorphismus ist keine Verletzung von LSP.
Telastyn
1
@ Alexander - Sicher. Nehmen wir an, dass es sich um eine Eigenschaft handelt. In diesem Fall verstößt Ihr Code gegen LSP, da Subtypen nicht dasselbe semantische Verhalten aufweisen können. Dynamische Sprachen oder Sprachen mit Ententyp sind jedoch nicht besonders speziell. Es gibt nichts, was sie in ihrem Sprachdesign tun, was die Verletzung verursacht. Der Code, den Sie geschrieben haben, funktioniert. Denken Sie daran, dass LSP eher ein Prinzip des Programmdesigns ist (daher das sollte in der Definition) als eine mathematische Eigenschaft von Programmen.
Telastyn
6
@Alexey: Wenn Sie etwas schreiben, das von x.class == A abhängt, dann ist es Ihr Code, der gegen LSP verstößt , nicht die Sprache. Es ist möglich, in fast jeder Programmiersprache Code zu schreiben, der gegen LSP verstößt.
Andres F.
7

Im Kontext von LSP ist eine "Eigenschaft" etwas, das an einem Typ (oder einem Objekt) beobachtet werden kann. Insbesondere handelt es sich um eine "nachweisbare Eigenschaft".

Eine solche "Eigenschaft" könnte das Vorhandensein einer foo()Methode sein, die keinen Rückgabewert hat (und dem in ihrer Dokumentation festgelegten Vertrag folgt).

Stellen Sie sicher, dass Sie diesen Begriff nicht mit "Eigenschaft" verwechseln, da in " classeine Eigenschaft jedes Objekts in Ruby ist". Eine solche "Eigenschaft" kann eine "LSP-Eigenschaft" sein, ist aber nicht automatisch dieselbe!

Die Antwort auf Ihre Fragen hängt nun stark davon ab, wie streng Sie "Eigenschaft" definieren. Wenn Sie sagen "die Eigenschaft der Klasse Aist, dass .classder Typ des Objekts zurückgegeben wird", dann hat diese Eigenschaft Btatsächlich .

Wenn Sie jedoch die "Eigenschaft" als " .classRückgabe A" definieren, Bverfügt diese Eigenschaft offensichtlich nicht über diese Eigenschaft.

Die zweite Definition ist jedoch nicht sehr nützlich, da Sie im Wesentlichen einen Umweg gefunden haben, um eine Konstante zu deklarieren.

Joachim Sauer
quelle
Ich kann mir nur eine Definition einer "Eigenschaft" eines Programms vorstellen: Für eine bestimmte Eingabe gibt sie einen bestimmten Wert zurück, oder allgemeiner, wenn sie als Block in einem anderen Programm verwendet wird, gibt dieses andere Programm für eine bestimmte Eingabe a zurück gegebene Werte. Mit dieser Definition sehe ich nicht, was es bedeutet, dass " .classden Typ des Objekts zurückgibt". Wenn dies bedeutet, x.class == x.classist dies keine interessante Eigenschaft.
Alexey
1
@Alexey: Ich habe meine Frage mit einer Klarstellung darüber aktualisiert, was "Eigenschaft" im Kontext von LSP bedeutet.
Joachim Sauer
2
@Alexey: Wenn ich in das Papier schaue, finde ich keine spezifische Definition oder "Eigenschaft". Das liegt wahrscheinlich daran, dass der Begriff im allgemeinen CS-Sinne verwendet wird "etwas, das über ein Objekt beobachtet / bewiesen werden kann". Es hat nichts mit dem anderen zu tun, das "ein Feld eines Objekts" bedeutet.
Joachim Sauer
4
@ Alexander: Ich weiß nicht, was ich dir noch sagen kann. Ich verwende die Definition von "eine Eigenschaft ist eine Qualität oder ein Attribut eines Objekts". "Farbe" ist eine Eigenschaft eines physischen, sichtbaren Objekts. "Dichte" ist eine Eigenschaft eines Materials. "mit einer bestimmten Methode" ist eine Eigenschaft einer Klasse / eines Objekts.
Joachim Sauer
4
@Alexey: Ich denke, du wirfst Baby mit dem Badewasser: Nur weil für einige Eigenschaften der LSP nicht zum Halten gebracht werden kann, heißt das nicht, dass er nutzlos ist oder "in keiner Sprache hält". Aber diese Diskussion würde hier zu weit gehen.
Joachim Sauer
5

So wie ich es verstehe, gibt es nichts an Selbstbeobachtung, das mit dem LSP nicht kompatibel wäre. Grundsätzlich sollten die beiden austauschbar sein, solange ein Objekt dieselben Methoden wie ein anderes unterstützt. Das heißt, wenn Ihr Code ein erwartetes AddressObjekt, dann spielt es keine Rolle , ob es ein ist CustomerAddressoder ein WarehouseAddress, solange beide bieten (zB) getStreetAddress(), getCityName(), getRegion()und getPostalCode(). Sie könnten sicherlich eine Art Dekorateur erstellen, der einen anderen Objekttyp verwendet und Introspektion verwendet, um die erforderlichen Methoden bereitzustellen (z. B. eine DestinationAddressKlasse, die ein ShipmentObjekt nimmt und die AddressLieferadresse als darstellt ), aber dies ist nicht erforderlich und würde es sicherlich nicht tun Verhindern Sie nicht, dass der LSP angewendet wird.

TMN
quelle
2
@Alexey: Die Objekte sind "gleich", wenn sie die gleichen Methoden unterstützen. Dies bedeutet denselben Namen, dieselbe Anzahl und denselben Typ von Argumenten, denselben Rückgabetyp und dieselben Nebenwirkungen (solange sie für den aufrufenden Code sichtbar sind). Die Methoden mögen sich völlig anders verhalten, aber solange sie den Vertrag einhalten, ist das in Ordnung.
TMN
1
@ Alexander: aber warum sollte ich so einen Vertrag haben? Welchen tatsächlichen Nutzen erfüllt dieser Vertrag? Wenn ich einen solchen Vertrag hatte , konnte ich einfach jedes Vorkommen von ersetzen x.class.namemit 'A' , was effektiv x.class.name nutzlos .
Joachim Sauer
1
@Alexey: nochmal: Nur weil Sie einen Vertrag definieren können, der nicht durch die Erweiterung einer anderen Klasse erfüllt werden kann, wird LSP nicht beschädigt. Es bedeutet nur, dass Sie eine nicht erweiterbare Klasse erstellt haben. Wenn ich eine Methode definiere, um "zurückzugeben, wenn der bereitgestellte Codeblock in endlicher Zeit endet ", habe ich auch einen Vertrag, der nicht erfüllt werden kann. Das bedeutet nicht, dass die Programmierung nutzlos ist.
Joachim Sauer
2
@Alexey versucht festzustellen, ob x.class.name == 'A'es sich bei der Ententypisierung um ein Anti-Muster handelt: Schließlich stammt die Ententypisierung von "Wenn sie quakt und wie eine Ente läuft, ist sie eine Ente". Wenn es sich also wie Adie Verträge verhält und Adiese A
einhält
1
@Alexey Ihnen wurden klare Definitionen präsentiert. Die Klasse eines Objekts ist nicht Teil seines Verhaltens oder Vertrags oder wie auch immer Sie es nennen möchten. Sie eindeutigen "Eigenschaft" mit "Objektfeld wie x.myField", was, wie bereits erwähnt, NICHT dasselbe ist. In diesem Zusammenhang ähnelt eine Eigenschaft eher einer mathematischen Eigenschaft, wie Typinvarianten. Darüber hinaus ist es ein Anti-Pattern, um den genauen Typ zu überprüfen, wenn Sie Enten tippen möchten. Also, was ist dein Problem wieder mit LSP und Enten-Typisierung? ;)
Andres F.
4

Nachdem ich Barbara Liskovs Originalarbeit durchgesehen habe, habe ich herausgefunden, wie man die Wikipedia-Definition vervollständigt, damit LSP tatsächlich in fast jeder Sprache zufrieden sein kann.

Zunächst ist das Wort "beweisbar" in der Definition wichtig. Es wird im Wikipedia-Artikel nicht erklärt, und "Einschränkungen" werden an anderer Stelle ohne Bezugnahme darauf erwähnt.

Hier ist das erste wichtige Zitat aus dem Papier:

Was benötigt wird, ist eine stärkere Anforderung, die das Verhalten von Untertypen einschränkt: Eigenschaften, die anhand der Spezifikation des vermuteten Typs eines Objekts nachgewiesen werden können, sollten gelten, obwohl das Objekt tatsächlich Mitglied eines Untertyps dieses Typs ist ...

Und hier ist die zweite, die erklärt, was eine Typenspezifikation ist:

Eine Typenspezifikation enthält die folgenden Informationen:

  • Der Name des Typs;
  • Eine Beschreibung des Wertebereichs des Typs;
  • Für jede Methode des Typs:
    • Seinen Namen;
    • Seine Unterschrift (einschließlich signalisierter Ausnahmen);
    • Sein Verhalten in Bezug auf Vor- und Nachbedingungen.

LSP ist also nur in Bezug auf bestimmte Typspezifikationen sinnvoll, und für eine geeignete Typspezifikation (zum Beispiel für die leere) kann es wahrscheinlich in jeder Sprache erfüllt werden.

Ich halte Telastyns Antwort für die Antwort , die ich gesucht habe, weil die "Einschränkungen" ausdrücklich erwähnt wurden.

Alexey
quelle
Telastyn, wenn Sie diese Zitate zu Ihrer Antwort hinzufügen könnten, würde ich lieber Ihre als meine akzeptieren.
Alexey
2
Das Markup ist nicht ganz dasselbe, und ich habe ein wenig an der Betonung geändert, aber die Zitate waren enthalten.
Telastyn
Entschuldigung, Joachim Sauer hat bereits "nachweisbare" Eigenschaften erwähnt, die Sie nicht mochten. Im Allgemeinen haben Sie lediglich vorhandene Antworten angepasst. Ehrlich gesagt, ich weiß nicht, wonach Sie suchen ...
Andres F.
Nein, es wurde nicht erklärt "nachweisbar von was?". Die Eigenschaft x.class.name = 'A'ist für alle xKlassen nachweisbar, Awenn Sie zu viel Wissen zulassen. Die Typenspezifikation wurde nicht definiert, und ihre genaue Beziehung zu LSP war auch nicht definiert, obwohl informell einige Angaben gemacht wurden. Ich habe bereits in Liskovs Artikel gefunden, wonach ich gesucht habe, und meine Frage oben beantwortet.
Alexey
Ich denke, die Worte, die Sie ermutigt haben, sind der Schlüssel. Wenn der Supertyp dokumentiert, dass für einen xdieser Typen ein Typ x.woozlevorliegt undefined, ist kein Typ, für den kein Typ x.woozlevorliegt undefined, ein geeigneter Subtyp. Wenn der Supertyp nichts dokumentiert x.woozle, würde die Tatsache, dass die Verwendung x.woozledes Supertyps zu einer Ausbeute führt undefined, nichts darüber bedeuten, was auf dem Subtyp geschehen könnte.
Supercat
3

Um den Artikel von Wikipedia über LSP zu zitieren : "Substituierbarkeit ist ein Prinzip in der objektorientierten Programmierung." Es ist ein Prinzip und Teil der Gestaltung Ihres Programms. Wenn Sie Code schreiben, der davon abhängt x.class == A, verstößt Ihr Code gegen LSP. Beachten Sie, dass diese Art von fehlerhaftem Code auch in Java möglich ist, ohne dass eine Ententypisierung erforderlich ist.

Nichts beim Entenschreiben bricht von Natur aus LSP. Nur wenn Sie es missbrauchen, wie in Ihrem Beispiel.

Zusätzlicher Gedanke: Besiegt die explizite Überprüfung der Klasse eines Objekts den Zweck der Ententypisierung überhaupt nicht?

Andres F.
quelle
Andres, können Sie bitte Ihre Definition von LSP geben?
Alexey
1
@Alexey Die genaue Definition von LSP wird in Wikipedia in Bezug auf Subtypen angegeben. Die informelle Definition lautet Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).. Die genaue Klasse eines Objekts ist NICHT eine der "wünschenswerten Eigenschaften des Programms"; Andernfalls würde dies nicht nur der Ententypisierung, sondern auch der Subtypisierung im Allgemeinen zuwiderlaufen, einschließlich Javas Geschmack.
Andres F.
2
@Alexey Beachten Sie auch , dass Ihr Beispiel x.class == Asowohl LSP verletzt und Ente eingeben. Es macht keinen Sinn, Enten zu tippen, wenn Sie nach den tatsächlichen Typen suchen.
Andres F.
Andres, diese Definition ist für mich nicht präzise genug, um sie zu verstehen. Welches Programm, ein bestimmtes oder eines? Was ist eine wünschenswerte Eigenschaft? Wenn sich die Klasse in einer Bibliothek befindet, können unterschiedliche Anwendungen unterschiedliche Eigenschaften für wünschenswert halten. A nicht sehen, wie die Codezeile LSP verletzen könnte, weil ich dachte, dass LSP eine Eigenschaft eines Klassenpaares in einer bestimmten Programmiersprache ist: entweder ( A, B) LSP erfüllen oder nicht. Wenn LSP von dem an anderer Stelle verwendeten Code abhängt, wird nicht erklärt, welcher Code zulässig ist. Ich hoffe, hier etwas zu finden: cse.ohio-state.edu/~neelam/courses/788/lwb.pdf
Alexey
2
@Alexey LSP gilt (oder nicht) für ein bestimmtes Design. Es ist etwas, wonach man in einem Design suchen muss; Es ist keine Eigenschaft einer Sprache im Allgemeinen. Genauer geht es nicht als die eigentliche Definition : Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T. Es ist offensichtlich, dass dies x.classhier keine der interessanten Eigenschaften ist. Andernfalls würde Javas Polymorphismus auch nicht funktionieren. Das Eingeben von Enten in Ihr "x.class-Problem" ist mit nichts verbunden . Stimmen Sie bisher zu?
Andres F.