¿El principio de sustitución de Liskov es incompatible con la introspección o la escritura de pato?

11

¿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 Bhereda de una clase A, entonces por cada objeto xde A, x.classva a regresar A, pero si xes un objeto de B, x.classno 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 Classclase es un descendiente de la Moduleclase. 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)
Alexey
fuente
2
Casi todos los lenguajes modernos proporcionan cierto grado de introspección, por lo que la pregunta no es realmente específica de Ruby.
Joachim Sauer
Entiendo, le di a Ruby solo como ejemplo. No sé, tal vez en algunos otros idiomas con introspección hay algunas "formas débiles" de LSP, pero, si entendí el principio correctamente, es incompatible con la introspección.
Alexey
He eliminado "Ruby" del título.
Alexey
2
La respuesta corta es que son compatibles. Aquí hay una publicación de blog con la que estoy más de acuerdo: Principio de sustitución de Liskov para Duck Typing
K.Steff
2
@Alexey Las propiedades en este contexto son invariantes de un objeto. Por ejemplo, los objetos inmutables tienen la propiedad de que sus valores no cambian. Si observa buenas pruebas unitarias, deberían probar exactamente estas propiedades.
K.Steff

Respuestas:

29

Aquí está el principio real :

Deje q(x)ser una propiedad demostrable sobre objetos xde tipo T. Entonces q(y)debería ser demostrable para objetos yde tipo Sdonde Ses un subtipo de T.

Y el excelente resumen de Wikipedia :

Establece que, en un programa de computadora, si S es un subtipo de T, entonces los objetos de tipo T pueden ser reemplazados por objetos de tipo S (es decir, los objetos de tipo S pueden ser sustituidos por objetos de tipo T) sin alterar ninguno de las propiedades deseables de ese programa (corrección, tarea realizada, etc.).

Y algunas citas relevantes del artículo:

Lo que se necesita es un requisito más estricto que limite el comportamiento de los subtipos: las propiedades que se pueden probar utilizando la especificación del tipo presunto de un objeto deben cumplir incluso si el objeto es realmente miembro de un subtipo de ese tipo ...

Una especificación de tipo incluye la siguiente información:
- El nombre del tipo;
- Una descripción del espacio de valores del tipo;
- Para cada uno de los métodos del tipo:
--- Su nombre;
--- Su firma (incluidas las excepciones señaladas);
--- Su comportamiento en términos de precondiciones y postcondiciones.

Entonces a la pregunta:

¿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?

No.

A.classdevuelve una clase
B.classdevuelve una clase

Como 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.classdevolvería un tipo con una restricción que debe ser Ao un subtipo de A. Esto proporciona la garantía estática de que cualquier subtipo de Adebe proporcionar un método T.classcuyo 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.

Telastyn
fuente
1
"Como puede hacer la misma llamada en el tipo más específico y obtener un resultado compatible, LSP se mantiene". LSP se mantiene si los resultados son idénticos, si lo entendí correctamente. Tal vez pueda haber alguna forma de "semana" de LSP con respecto a las restricciones dadas, requiriendo en lugar de todas las propiedades que solo se cumplan las restricciones dadas. En cualquier caso, agradecería cualquier referencia.
Alexey
@Alexey editado para incluir lo que significa LSP. Si puedo usar B donde espero A, entonces se mantiene LSP. Tengo curiosidad por cómo crees que Ruby .class posiblemente viola eso.
Telastyn
3
@Alexey: si su programa contiene fail unless x.foo == 42y 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.
Telastyn
1
@Alexey - Claro. Asumamos que es una propiedad. En ese caso, su código viola el LSP ya que no permite que los subtipos tengan el mismo comportamiento semántico. Pero no es particularmente especial para lenguajes dinámicos o de tipo pato. No hay nada que hagan en su diseño de lenguaje que cause la violación. El código que escribiste sí. Recuerde, el LSP es un principio del diseño del programa (de ahí el deber en la definición) más que una propiedad matemática de los programas.
Telastyn
66
@Alexey: si escribe algo que depende de x.class == A, entonces es su código el que viola el LSP , no el lenguaje. Es posible escribir código que viole LSP en casi todos los lenguajes de programación.
Andres F.
7

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 " classes 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 Aes que .classse devuelva el tipo de objeto", a continuación, Ben realidad lo hace tener esa propiedad.

Sin embargo, si se define la "propiedad" de ser " .classretornos A", entonces, evidentemente, Bno 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.

Joachim Sauer
fuente
Solo puedo pensar en una definición de una "propiedad" de un programa: para una entrada dada, devuelve un valor dado o, más generalmente, cuando se usa como un bloque en otro programa, ese otro programa para una entrada dada devolverá un valores dados Con esta definición, no veo lo que significa que " .classdevolverá el tipo del objeto". Si eso significa eso x.class == x.class, esta no es una propiedad interesante.
Alexey
1
@Alexey: He actualizado mi pregunta con una aclaración sobre lo que significa "propiedad" en el contexto de LSP.
Joachim Sauer
2
@Alexey: mirando en el periódico no encuentro una definición o "propiedad" específica. Probablemente sea porque el término se usa en el sentido general de CS "algo que se puede observar / probar sobre algún objeto". No tiene nada que ver con el otro significado de "un campo de un objeto".
Joachim Sauer
44
@Alexey: No sé qué más puedo decirte. Utilizo la definición de "una propiedad es alguna cualidad o atributo de un objeto". "color" es una propiedad de un objeto físico, visible. La "densidad" es una propiedad de un material. "tener un método especificado" es una propiedad de una clase / objeto.
Joachim Sauer
44
@Alexey: Creo que estás tirando al bebé con el agua del baño: solo porque para algunas propiedades no se puede hacer que el LSP retenga, no significa que sea inútil o "no retenga en ningún idioma". Pero esa discusión llegaría muy lejos aquí.
Joachim Sauer
5

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 Addressobjeto, entonces no importa si es un CustomerAddresso una WarehouseAddress, siempre y cuando ambos proporcionan (por ejemplo) getStreetAddress(), getCityName(), getRegion()y getPostalCode(). 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, una DestinationAddressclase que tome un Shipmentobjeto y presente la dirección de envío como un Address), pero no es obligatorio y ciertamente no No evite que se aplique el LSP.

TMN
fuente
2
@Alexey: los objetos son "iguales" si admiten los mismos métodos. Esto significa el mismo nombre, el mismo número y tipo de argumentos, el mismo tipo de retorno y los mismos efectos secundarios (siempre que sean visibles para el código de llamada). Los métodos pueden comportarse de manera completamente diferente, pero siempre y cuando cumplan con el contrato, está bien.
TMN
1
@Alexey: pero ¿por qué tendría un contrato así? ¿Qué uso real cumple ese contrato? Si tuviera un contrato de este tipo yo podría simplemente reemplazar cada ocurrencia de x.class.namecon 'A' , por lo que efectivamente x.class.name inútil .
Joachim Sauer
1
@Alexey: nuevamente: solo porque puedes definir un contrato que no se puede cumplir al extender otra clase no se rompe el LSP. Simplemente significa que has creado una clase no extensible. Si defino un método para "devolver si el bloque de código proporcionado termina en un tiempo finito ", entonces también tengo un contrato que no se puede cumplir. No significa que la programación sea inútil.
Joachim Sauer
2
@Alexey tratando de determinar si se 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 como Ay honra los contratos que se Apresentan, es una Ainstancia
K.Steff
1
@Alexey Se te presentaron definiciones claras. La clase de un objeto no es parte de su comportamiento o contrato o como quiera llamarlo. Está confundiendo "propiedad" con "campo de objeto, como x.myField", que, como se ha señalado, NO es lo mismo. En este contexto, una propiedad es más como una propiedad matemática , como invariantes de tipo. Además, es un antipatrón para verificar el tipo exacto si desea escribir pato. Entonces, ¿cuál es su problema con el LSP y la escritura de pato, de nuevo? ;)
Andres F.
4

Despué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:

Lo que se necesita es un requisito más estricto que limite el comportamiento de los subtipos: las propiedades que pueden probarse utilizando la especificación del tipo presunto de un objeto deben cumplir incluso si el objeto es en realidad miembro de un subtipo de ese tipo ...

Y aquí está el segundo, explicando qué es una especificación de tipo :

Una especificación de tipo incluye la siguiente información:

  • El nombre del tipo;
  • Una descripción del espacio de valores del tipo;
  • Para cada uno de los métodos del tipo:
    • Su nombre;
    • Su firma (incluidas las excepciones señaladas);
    • Su comportamiento en términos de precondiciones y postcondiciones.

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.

Alexey
fuente
Telastyn, si pudieras agregar estas citas a tu respuesta, preferiría aceptar las tuyas que las mías.
Alexey
2
el marcado no es el mismo, y cambié un poco el énfasis, pero se incluyeron las citas.
Telastyn
Lo sentimos, Joachim Sauer ya mencionó propiedades "demostrables", que no le gustaron. En general, simplemente ha reformulado las respuestas existentes. Honestamente, no sé lo que estás buscando ...
Andres F.
No, no se explicó "¿demostrable de qué?". La propiedad x.class.name = 'A'es demostrable para toda la xclase Asi 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.
Alexey
Creo que las palabras que envalentonaste son la clave. Si el supertipo documenta que para cualquiera xde ese tipo x.woozlerendirá undefined, entonces ningún tipo para el que x.woozleno rinda undefinedserá un subtipo adecuado. Si el supertipo no documenta nada x.woozle, el hecho de que el uso x.woozledel supertipo produzca undefinedno implicaría nada acerca de lo que podría hacer en el subtipo.
supercat
3

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?

Andres F.
fuente
Andrés, ¿puedes dar tu definición de LSP, por favor?
Alexey
1
@Alexey La definición precisa de LSP se establece en Wikipedia en términos de subtipos. La definición informal es 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.
Andres F.
2
@Alexey También tenga en cuenta que su ejemplo de x.class == Aviola el LSP y el tipeo de pato. No tiene sentido usar duck typing si va a verificar los tipos reales.
Andres F.
Andrés, esta definición no es lo suficientemente precisa para que yo entienda. ¿Qué programa, uno dado o alguno? ¿Qué es una propiedad deseable? Si la clase está en una biblioteca, diferentes aplicaciones pueden considerar diferentes propiedades deseables. A no ven la forma en la línea de código podría violar la LSP, porque pensé que era una propiedad LSP de un par de clases en un lenguaje de programación determinado: o ( 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.pdf
Alexey
2
@Alexey LSP se mantiene (o no) para un diseño específico. Es algo a buscar en un diseño; No es propiedad de un idioma en general. No hay nada más preciso que la propia definición: 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 obvio que x.classno 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?
Andres F.