¿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)
Respuestas:
Aquí está el principio real :
Y el excelente resumen de Wikipedia :
Y algunas citas relevantes del artículo:
Entonces a la pregunta:
No.
A.class
devuelve una claseB.class
devuelve una claseComo puede hacer la misma llamada en el tipo más específico y obtener un resultado compatible, LSP se mantiene. El problema es que con los lenguajes dinámicos, aún puede llamar a las cosas en el resultado esperando que estén allí.
Pero consideremos un lenguaje tipado estático, estructural (pato). En este caso,
A.class
devolvería un tipo con una restricción que debe serA
o un subtipo deA
. Esto proporciona la garantía estática de que cualquier subtipo deA
debe proporcionar un métodoT.class
cuyo resultado sea un tipo que satisfaga esa restricción.Esto proporciona una afirmación más fuerte que LSP tiene en los idiomas que admiten la escritura de pato, y que cualquier violación de LSP en algo como Ruby ocurre más debido al mal uso dinámico normal que a una incompatibilidad de diseño de lenguaje.
fuente
fail unless x.foo == 42
y un subtipo devuelve 0, eso es lo mismo. Esto no es una falla de LSP, es la operación normal de su programa. El polimorfismo no es una violación de LSP.En el contexto de LSP, una "propiedad" es algo que se puede observar en un tipo (o un objeto). En particular, habla de una "propiedad demostrable".
Tal "propiedad" podría ser la existencia de un
foo()
método que no tiene valor de retorno (y sigue el contrato establecido en su documentación).Asegúrese de no confundir este término con "propiedad" como en "
class
es una propiedad de cada objeto en Ruby". Tal "propiedad" puede ser una "propiedad LSP", ¡pero no es automáticamente la misma!Ahora la respuesta a sus preguntas depende mucho de cuán estricto defina "propiedad". Si usted dice "la propiedad de la clase
A
es que.class
se devuelva el tipo de objeto", a continuación,B
en realidad lo hace tener esa propiedad.Sin embargo, si se define la "propiedad" de ser "
.class
retornosA
", entonces, evidentemente,B
no no tienen esa propiedad.Sin embargo, la segunda definición no es muy útil, ya que esencialmente ha encontrado una forma redonda de declarar una constante.
fuente
.class
devolverá el tipo del objeto". Si eso significa esox.class == x.class
, esta no es una propiedad interesante.Según tengo entendido, no hay nada sobre la introspección que sea incompatible con el LSP. Básicamente, siempre que un objeto admita los mismos métodos que otro, los dos deben ser intercambiables. Es decir, si el código espera un
Address
objeto, entonces no importa si es unCustomerAddress
o unaWarehouseAddress
, siempre y cuando ambos proporcionan (por ejemplo)getStreetAddress()
,getCityName()
,getRegion()
ygetPostalCode()
. Ciertamente, podría crear algún tipo de decorador que tome un tipo diferente de objeto y use la introspección para proporcionar los métodos requeridos (por ejemplo, unaDestinationAddress
clase que tome unShipment
objeto y presente la dirección de envío como unAddress
), pero no es obligatorio y ciertamente no No evite que se aplique el LSP.fuente
x.class.name
con'A'
, por lo que efectivamentex.class.name
inútil .x.class.name == 'A'
trata de un antipatrón en la escritura de patos: después de todo, la escritura de patos proviene de "Si grazna y camina como un pato, es un pato". Entonces, si se comporta comoA
y honra los contratos que seA
presentan, es unaA
instanciaDespués de leer el artículo original de Barbara Liskov, descubrí cómo completar la definición de Wikipedia para que el LSP pueda satisfacerse en casi cualquier idioma.
En primer lugar, la palabra "demostrable" es importante en la definición. No se explica en el artículo de Wikipedia, y las "restricciones" se mencionan en otra parte sin referencia a ella.
Aquí está la primera cita importante del artículo:
Y aquí está el segundo, explicando qué es una especificación de tipo :
Por lo tanto, LSP solo tiene sentido con respecto a las especificaciones de tipo dadas , y para una especificación de tipo apropiada (para la vacía, por ejemplo), puede satisfacerse probablemente en cualquier idioma.
Considero que la respuesta de Telastyn es la más cercana a lo que estaba buscando porque las "restricciones" se mencionaron explícitamente.
fuente
x.class.name = 'A'
es demostrable para toda lax
claseA
si permite demasiado conocimiento. La especificación de tipo no estaba definida, y su relación exacta con LSP tampoco, aunque informalmente se dieron algunas indicaciones. Ya encontré lo que estaba buscando en el artículo de Liskov y respondí mi pregunta anterior.x
de ese tipox.woozle
rendiráundefined
, entonces ningún tipo para el quex.woozle
no rindaundefined
será un subtipo adecuado. Si el supertipo no documenta nadax.woozle
, el hecho de que el usox.woozle
del supertipo produzcaundefined
no implicaría nada acerca de lo que podría hacer en el subtipo.Para citar el artículo de Wikipedia sobre LSP , "la sustituibilidad es un principio en la programación orientada a objetos". Es un principio y parte del diseño de su programa. Si escribe un código que depende
x.class == A
, entonces es su código el que está violando el LSP. Tenga en cuenta que este tipo de código roto también es posible en Java, no es necesario escribir pato.Nada en el tipeo de patos rompe inherentemente el LSP. Solo si lo usas mal, como en tu ejemplo.
Pensamiento adicional: de todos modos, ¿no verifica explícitamente la clase de un objeto el propósito de escribir pato?
fuente
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).
. La clase exacta de un objeto NO es una de "las propiedades deseables del programa"; de lo contrario, sería contrario no solo a la escritura de pato sino a la subtipificación en general, incluido el sabor de Java.x.class == A
viola el LSP y el tipeo de pato. No tiene sentido usar duck typing si va a verificar los tipos reales.A
,B
) satisfacen LSP o no. Si LSP depende del código utilizado en otro lugar, no se explica qué código está permitido. Espero encontrar algo aquí: 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 obvio quex.class
no es una de las propiedades interesantes aquí; de lo contrario, el polimorfismo de Java tampoco funcionaría. No hay nada inherente a escribir pato en su "problema x.class". ¿Estás de acuerdo hasta ahora?