¿Entiendo correctamente que el Principio de sustitución de Liskov no se puede observar en idiomas en los que los objetos pueden inspeccionarse a sí mismos, como lo que es habitual en los idiomas con tipo de pato?
Por ejemplo, en Ruby, si una clase B
hereda de una clase A
, entonces por cada objeto x
de A
, x.class
va a regresar A
, pero si x
es un objeto de B
, x.class
no va a regresar A
.
Aquí hay una declaración de LSP:
Deje que q (x) sea un demostrable propiedad sobre los objetos x de tipo T . Entonces q (y) debe ser demostrable para los objetos y de tipo S , donde S es un subtipo de T .
Entonces en Ruby, por ejemplo,
class T; end
class S < T; end
violar LSP de esta forma, como lo atestigua la propiedad q (x) =x.class.name == 'T'
Adición. Si la respuesta es "sí" (LSP incompatible con la introspección), entonces mi otra pregunta sería: ¿hay alguna forma "débil" modificada de LSP que posiblemente pueda ser válida para un lenguaje dinámico, posiblemente bajo algunas condiciones adicionales y solo con tipos especiales de propiedades .
Actualizar. Como referencia, aquí hay otra formulación de LSP que he encontrado en la web:
Las funciones que usan punteros o referencias a clases base deben poder usar objetos de clases derivadas sin saberlo.
Y otro:
Si S es un subtipo declarado de T, los objetos de tipo S deberían comportarse como se espera que se comporten los objetos de tipo T, si se tratan como objetos de tipo T.
El último está anotado con:
Tenga en cuenta que el LSP tiene que ver con el comportamiento esperado de los objetos. Solo se puede seguir el LSP si se tiene claro cuál es el comportamiento esperado de los objetos.
Esto parece ser más débil que el original, y podría ser posible observarlo, pero me gustaría verlo formalizado, en particular explicado quién decide cuál es el comportamiento esperado.
¿Entonces LSP no es una propiedad de un par de clases en un lenguaje de programación, sino de un par de clases junto con un conjunto dado de propiedades, satisfechas por la clase ancestro? En la práctica, ¿significaría esto que para construir una subclase (clase descendiente) que respete LSP, deben conocerse todos los usos posibles de la clase ancestro? Según LSP, se supone que la clase ancestro es reemplazable por cualquier clase descendiente, ¿verdad?
Actualizar. Ya acepté la respuesta, pero me gustaría agregar un ejemplo más concreto de Ruby para ilustrar la pregunta. En Ruby, cada clase es un módulo en el sentido de que la Class
clase es un descendiente de la Module
clase. Sin embargo:
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)