Ho ben capito che il principio di sostituzione di Liskov non può essere osservato nelle lingue in cui gli oggetti possono ispezionare se stessi, come quello che è normale in lingue dattilografate?
Ad esempio, in Ruby, se una classe B
eredita da una classe A
, quindi per ogni oggetto x
di A
, x.class
restituirà A
, ma se x
è un oggetto di B
, x.class
non restituirà A
.
Ecco una dichiarazione di LSP:
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.
Quindi, in Ruby, per esempio,
class T; end
class S < T; end
violare LSP in questo modulo, come testimoniato dalla proprietà q (x) = x.class.name == 'T'
Addizione. Se la risposta è "sì" (LSP incompatibile con l'introspezione), allora la mia altra domanda sarebbe: c'è qualche forma "debole" modificata di LSP che può eventualmente contenere per una dinamica linguaggio, eventualmente in alcune condizioni aggiuntive e con solo tipi speciali di proprietà .
Aggiornamento. Per riferimento, ecco un'altra formulazione di LSP che ho trovato sul web:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
E un altro:
If S is a declared subtype of T, objects of type S should behave as objects of type T are expected to behave, if they are treated as objects of type T.
L'ultimo è annotato con:
Note that the LSP is all about expected behaviour of objects. One can only follow the LSP if one is clear about what the expected behaviour of objects is.
Questo sembra essere più debole di quello originale e potrebbe essere possibile osservarlo, ma mi piacerebbe vederlo formalizzato, in particolare ha spiegato chi decide quale sia il comportamento previsto.
Quindi LSP non è una proprietà di una coppia di classi in un linguaggio di programmazione, ma di una coppia di classi insieme a un determinato insieme di proprietà, soddisfatte dalla classe antenata? In pratica, ciò significherebbe che per costruire una sottoclasse (classe discendente) nel rispetto dell'LSP, tutti i possibili usi della classe antenata devono essere conosciuti? Secondo LSP, la classe degli antenati dovrebbe essere sostituibile con qualsiasi classe discendente, giusto?
Aggiornamento. Ho già accettato la risposta, ma vorrei aggiungere un altro esempio concreto di Ruby per illustrare la domanda. In Ruby, ogni classe è un modulo nel senso che Class
class è un discendente di Module
class. Tuttavia:
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)