De Wikipedia, la enciclopedia libre
Ir a navegaciónSaltar a buscar

En la programación orientada a objetos , el subtipo de comportamiento es el principio de que las subclases deben satisfacer las expectativas de los clientes que acceden a objetos de subclase a través de referencias de tipo superclase, no solo en lo que respecta a la seguridad sintáctica (como la ausencia de errores de "método no encontrado") sino también en lo que respecta a la corrección del comportamiento. Específicamente, las propiedades que los clientes pueden probar usando la especificación del presunto tipo de un objeto deben ser válidas aunque el objeto sea en realidad un miembro de un subtipo de ese tipo.[1]

Por ejemplo, considere un tipo de pila y un tipo de cola, que ambos tienen una puesta método para agregar un elemento y un get método para eliminar una. Suponga que la documentación asociada con estos tipos especifica que los métodos de tipo Stack se comportarán como se espera para las pilas (es decir, exhibirán un comportamiento LIFO ), y que los métodos de tipo Queue se comportarán como se espera para las colas (es decir, exhibirán un comportamiento FIFO ). Supongamos, ahora, que el tipo Stack se declara como una subclase del tipo Queue. La mayoría de los compiladores de lenguajes de programación ignoran la documentación y realizan solo las comprobaciones necesarias para preservar la seguridad de tipos. Dado que, para cada método de tipo Cola, type Stack proporciona un método con un nombre y una firma coincidentes, esta comprobación se realizará correctamente. Sin embargo, los clientes que acceden a un objeto Stack a través de una referencia de tipo Queue, según la documentación de Queue, esperarían un comportamiento FIFO pero observarían el comportamiento LIFO, invalidando las pruebas de corrección de estos clientes y potencialmente conduciendo a un comportamiento incorrecto del programa en su conjunto.

Este ejemplo viola el subtipo de comportamiento porque el tipo Stack no es un subtipo de comportamiento del tipo Queue: no es el caso que el comportamiento descrito por la documentación del tipo Stack (es decir, el comportamiento LIFO) cumpla con la documentación del tipo Queue (que requiere un comportamiento FIFO) .

Por el contrario, un programa en el que tanto Stack como Queue son subclases de un tipo Bag, cuya especificación para get es simplemente que elimina algún elemento, satisface el subtipo de comportamiento y permite a los clientes razonar con seguridad sobre la corrección en función de los tipos presuntos de los objetos que interactuar con. De hecho, cualquier objeto que satisfaga la especificación Stack o Queue también satisface la especificación Bag.

Es importante destacar que si un tipo S es un subtipo de comportamiento de un tipo T depende únicamente de la especificación (es decir, la documentación ) del tipo T; la implementación del tipo T, si tiene alguna, es completamente irrelevante para esta pregunta. De hecho, el tipo T ni siquiera necesita tener una implementación; podría ser una clase puramente abstracta. Como otro ejemplo, el tipo Stack anterior es un subtipo de comportamiento del tipo Bag incluso si la implementación del tipo Bag exhibe un comportamiento FIFO: lo que importa es que la especificación del tipo Bag no especifica qué elemento se elimina mediante el método get. Esto también significa que la subtipificación conductual se puede discutir solo con respecto a una especificación particular (conductual) para cada tipo involucrado, y que si los tipos involucrados no tienen una especificación conductual bien definida, la subtipificación conductual no se puede discutir de manera significativa.

Verificación de subtipos de comportamiento

Un tipo S es un subtipo de comportamiento de un tipo T si cada comportamiento permitido por la especificación de S también está permitido por la especificación de T. Esto requiere, en particular, que para cada método M de T, la especificación de M en S sea más fuerte que el de T.

Una especificación de método dada por una condición previa P s y una condición posterior Q s es más fuerte que una dada por una condición previa P t y una condición posterior Q t (formalmente: (P s , Q s ) ⇒ (P t , Q t )) si P s es más débil que P t (es decir, P t implica P s ) y Q s es más fuerte que Q t (es decir, Q s implica Q t). Es decir, se puede fortalecer la especificación de un método fortaleciendo la condición posterior y debilitando la condición previa. De hecho, la especificación de un método es más sólida si impone restricciones más específicas a las salidas de las entradas que ya estaban respaldadas, o si requiere que se respalden más entradas.

Por ejemplo, considere la especificación (muy débil) de un método que calcula el valor absoluto de un argumento x , que especifica una condición previa 0 ≤ x y una condición posterior 0 ≤ resultado. Esta especificación dice que el método no necesita admitir valores negativos para x , y solo necesita asegurarse de que el resultado no sea negativo también. Dos formas posibles de fortalecer esta especificación son fortaleciendo la condición posterior para indicar el resultado = | x |, es decir, el resultado es igual al valor absoluto de x, o debilitando la condición previa a "verdadero", es decir, todos los valores para x deben ser compatibles . Por supuesto, también podemos combinar ambos en una especificación que establezca que el resultado debe ser igual al valor absoluto de x , para cualquier valor de x .

Sin embargo, tenga en cuenta que es posible fortalecer una especificación ((P s , Q s ) ⇒ (P t , Q t )) sin fortalecer la condición posterior (Q s ⇏ Q t ). [2] [3] Considere una especificación para el método de valor absoluto que especifica una condición previa 0 ≤ x y un resultado posterior a la condición = x. La especificación que especifica una condición previa "verdadero" y un resultado posterior a la condición = | x | refuerza esta especificación, aunque el resultado posterior a la condición = | x | no fortalece (ni debilita) el resultado posterior a la condición = x. La condición necesaria para una especificación con condición previa P s y condición posterior Q sser más fuerte que una especificación con la condición previa P t y la condición posterior Q t es que P s es más débil que P t y "Q s o no P s " es más fuerte que "Q t o no P t ". De hecho, "resultado = | x | o falso" fortalece "resultado = x o x <0".

"Sustituibilidad"

En un influyente discurso de apertura [4] sobre abstracción de datos y jerarquías de clases en la conferencia de investigación de lenguajes de programación OOPSLA 1987, Barbara Liskov dijo lo siguiente: "Lo que se busca aquí es algo como la siguiente propiedad de sustitución: Si para cada objeto de tipo S hay un objeto de tipo T tal que para todos los programas P definidos en términos de T, el comportamiento de P no cambia cuando es sustituido por , entonces S es un subtipo de T. "Desde entonces, esta caracterización ha sido ampliamente conocida como el Principio de sustitución de Liskov (LSP) . Desafortunadamente, sin embargo, tiene varios problemas. En primer lugar, en su formulación original, es demasiado fuerte: rara vez queremos el comportamiento de una subclase sea idéntico al de su superclase; la sustitución de un objeto de subclase por un objeto de superclase se hace a menudo con la intención de cambiar el comportamiento del programa, aunque, si se respeta el subtipo de comportamiento, de una manera que mantenga el programa deseable En segundo lugar, no hace mención de especificaciones , por lo que invita a una lectura incorrecta donde se compara la implementación del tipo S con la implementaciónde tipo T. Esto es problemático por varias razones, una de las cuales es que no es compatible con el caso común en el que T es abstracto y no tiene implementación. En tercer lugar, y más sutilmente, en el contexto de la programación imperativa orientada a objetos es difícil definir con precisión qué significa cuantificar universal o existencialmente sobre objetos de un tipo dado, o sustituir un objeto por otro. [3] En el ejemplo anterior, no estamos sustituyendo un objeto Stack por un objeto Bag, simplemente estamos usando un objeto Stack como un objeto Bag.

En una entrevista en 2016, la propia Liskov explica que lo que presentó en su discurso de apertura fue una "regla informal", que Jeannette Wing propuso más tarde que "intentaran averiguar con precisión qué significa esto", lo que llevó a su publicación conjunta [1 ] sobre la subtipificación conductual y, de hecho, "técnicamente, se llama subtipificación conductual". [5] Durante la entrevista, ella no usa terminología de sustitución para discutir los conceptos.

Notas

  1. a b Liskov, Barbara; Wing, Jeannette (1 de noviembre de 1994). "Una noción conductual de subtipificación". Transacciones ACM sobre lenguajes y sistemas de programación . 16 (6): 1811–1841. doi : 10.1145 / 197320.197383 .
  2. ^ Parkinson, Matthew J. (2005). Razonamiento local para Java (PDF) (PhD). Universidad de Cambridge.
  3. ^ a b Levaduras, Gary T .; Naumann, David A. (agosto de 2015). "Subtipado de comportamiento, herencia de especificación y razonamiento modular" . Transacciones ACM sobre lenguajes y sistemas de programación . 37 (4). doi : 10.1145 / 2766446 .
  4. ^ Liskov, B. (mayo de 1988). "Discurso de apertura: abstracción y jerarquía de datos". Avisos ACM SIGPLAN . 23 (5): 17–34. doi : 10.1145 / 62139.62141 .
  5. ^ van Vleck, Tom (20 de abril de 2016). Entrevista a Barbara Liskov . ACM.

Referencias

  • Parkinson, Matthew J .; Bierman, Gavin M. (enero de 2008). "Lógica de separación, abstracción y herencia". Avisos ACM SIGPLAN . 43 (1): 75–86. doi : 10.1145 / 1328897.1328451 .