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 B
erbt von einer Klasse A
, dann für jedes Objekt x
von A
, x.class
nach Rückkehr geht A
, aber wenn x
ein Gegenstand ist B
, x.class
wird 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 Class
Klasse ein Nachkomme der Module
Klasse 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)
Antworten:
Hier ist das eigentliche Prinzip :
Und die ausgezeichnete Wikipedia- Zusammenfassung:
Und einige relevante Zitate aus dem Papier:
Also weiter zur Frage:
Nein.
A.class
gibt eine Klasse zurück.B.class
gibt 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.class
würde ein Typ mit einer Einschränkung zurückgegeben, die er sein muss,A
oder ein Subtyp vonA
. Dies bietet die statische Garantie, dass jeder Subtyp vonA
eine Methode bereitstellen muss,T.class
deren 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.
quelle
fail unless x.foo == 42
und 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.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 "
class
eine 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
A
ist, dass.class
der Typ des Objekts zurückgegeben wird", dann hat diese EigenschaftB
tatsächlich .Wenn Sie jedoch die "Eigenschaft" als "
.class
RückgabeA
" definieren,B
verfü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.
quelle
.class
den Typ des Objekts zurückgibt". Wenn dies bedeutet,x.class == x.class
ist dies keine interessante Eigenschaft.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
Address
Objekt, dann spielt es keine Rolle , ob es ein istCustomerAddress
oder einWarehouseAddress
, solange beide bieten (zB)getStreetAddress()
,getCityName()
,getRegion()
undgetPostalCode()
. Sie könnten sicherlich eine Art Dekorateur erstellen, der einen anderen Objekttyp verwendet und Introspektion verwendet, um die erforderlichen Methoden bereitzustellen (z. B. eineDestinationAddress
Klasse, die einShipment
Objekt nimmt und dieAddress
Lieferadresse als darstellt ), aber dies ist nicht erforderlich und würde es sicherlich nicht tun Verhindern Sie nicht, dass der LSP angewendet wird.quelle
x.class.name
mit'A'
, was effektivx.class.name
nutzlos .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 wieA
die Verträge verhält undA
dieseA
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:
Und hier ist die zweite, die erklärt, was eine Typenspezifikation ist:
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.
quelle
x.class.name = 'A'
ist für allex
Klassen nachweisbar,A
wenn 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.x
dieser Typen ein Typx.woozle
vorliegtundefined
, ist kein Typ, für den kein Typx.woozle
vorliegtundefined
, ein geeigneter Subtyp. Wenn der Supertyp nichts dokumentiertx.woozle
, würde die Tatsache, dass die Verwendungx.woozle
des Supertyps zu einer Ausbeute führtundefined
, nichts darüber bedeuten, was auf dem Subtyp geschehen könnte.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?
quelle
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.x.class == A
sowohl LSP verletzt und Ente eingeben. Es macht keinen Sinn, Enten zu tippen, wenn Sie nach den tatsächlichen Typen suchen.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.pdfLet 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 diesx.class
hier 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?