31 de septiembre de 2003, Vol. 4, No. 5. ISSN: 1607-5079
 
 
[contenidos de RDU...] [Ver ejemplares anteriores de RDU...] [Volver a la portada de RDU] [Busca en los archivos de RDU] [Recomienda RDU a un amigo]  

Gerardo Rossel
grossel@dc.uba.ar
[¿Cómo citar este artículo]
Andrea Manna
amanna@dc.uba.ar
[Bajar este artículo]

En este apartado, se discutirá como afecta la herencia a los contratos. Al generar una subclase de una clase existente, es posible que sea necesario redefinir algunas características. Surgen naturalmente las preguntas ¿qué pasa con los contratos? ¿qué ocurre con el invariante de clase?

Se analizará primero el caso del invariante de clase. Se rige por la regla de la herencia del invariante (Invariant inheritance rule) (Meyer,1997):

  • La propiedad invariante de una clase es la conjunción lógica (and) de las aserciones que aparecen en su cláusula invariante y de las propiedades invariantes de sus padres, si hay alguno.

Se puede ver que esta regla es recursiva comprendiendo así a los invariantes de todos los ancestros. La regla anterior surge naturalmente de la propia naturaleza de la herencia. Las instancias de una clase son también instancias de los ancestros. Se tiene la siguiente jerarquía de clases:

 


Los triángulos y los rectángulos son a su vez polígonos. Los polígonos tienen por lo menos tres lados. Esta restricción se puede expresar como un invariante de clase : cant_lados >= 3. Para que los triángulos y rectángulos sean polígonos, deben respetar dicha restricción. Dada la regla de la herencia del invariante, ambos tienen como propiedad invariante cant_lados >= 3.

Ahora bien, como los triángulos tienen tres lados y los rectángulos cuatro lados, se puede agregar a cada una de estas clases, una cláusula invariante para expresar dichas restricciones.


La regla de herencia del invariante permite razonar acerca de la corrección de las clases y de la jerarquía de herencia. Si hubiera una subclase de polígono con una cláusula invariante indicando, por ejemplo, cant_lados = 2, dicha clase sería inválida ya que la propiedad invariante sería:

cant_lados >= 3 and cant_lados = 2

Esto indica que se está usando mal la herencia ya que, o bien, los polígonos pueden tener menos de tres lados o la nueva clase no es subclase de polígono (no es un polígono).


 
Cuando se redeclaran rutinas, es necesario respetar las aserciones de la rutina original. Es decir, no debería romperse el contrato establecido si se utiliza una rutina de una subclase. Las clases heredan los contratos de sus ancestros y deben respetarlos. Es posible, sin embargo, redefinir el contrato usando la metáfora del subcontrato. No siempre el que acuerda un contrato es el que lo lleva a cabo, en algunas ocasiones es posible subcontratar. Manteniendo las restricciones de tipificación, es posible que en tiempo de ejecución se ejecute una versión redefinida (vía herencia) de la rutina que tenía el contrato original; en dicho caso, la nueva rutina debe respetar el contrato. En el ejemplo de “Green Delivery”, se tendría una clase llamada GREEN_DELIVERY con una rutina entregar que devuelve la fecha y hora de la entrega.

Para simplificar, se muestra sólo la precondición correspondiente al peso del paquete y se asume una característica now que devuelve la hora actual. Supóngase ahora una subclase de GREEN_DELIVERY llamada GREEN_DELIVERY_AUX . En la figura se ve la situación: el símbolo ? representa una precondición y el símbolo ! la poscondición, de acuerdo a la notación BON (Waldén,1995 )


Supóngase que se redeclara la rutina entregar pero cambiando la precondición a paquetes de menos de 10 kilogramos. ¿Es esto correcto?. La redeclaración mencionada podría dar lugar a errores. Al tener enlace (binding) dinámico y comportamiento polimórfico, es posible que una variable declarada en el texto del programa como GREEN_DELIVERY, tenga asignado en tiempo de ejecución, un objeto instancia de GREEN_DELIVERY_AUX.

gd: GREEN_DELIVERY, gda: GREEN_DELIVERY_AUX
p: PAQUETE, d: DESTINO, f: DATE _ TIME
........ creación de objetos
....... operaciones varias
......................
gd := gda – asignación polimórfica
.....................
f := gd.entregar( p, d) -- ¿qué ocurre aquí?

Si el paquete pesa más de 10 kilogramos se tendría (a priori) una violación de la precondición ya que el tipo dinámico de gd es GREEN_DELIVERY_AUX. Pero, como fue declarado de tipo GREEN_DELIVERY, era razonable suponer que el paquete a enviar podía pesar hasta 35 kilogramos. Si la precondición de GREEN_DELIVERY_AUX fuera que el peso del paquete puede ser hasta 50 Kg., no habría problemas ya que es una precondición más débil y por lo tanto las llamadas a la rutina enviar que cumplan con la precondición de GREEN_DELIVERY, también cumplirán con la precondición de GREEN_DELIVERY_AUX.


La regla, en lo que respecta a precondiciones, para redefinir rutinas es sencilla:
  • La nueva rutina debe mantener o debilitar la precondición de la rutina original

Dada la complejidad que implica para un compilador determinar si una aserción es más fuerte o débil que otra, lo que se hace en realidad es un o lógico entre la precondición de la rutina original y la nueva precondición de la rutina redeclarada. En Eiffel se escribe de la siguiente manera:

require else nueva_precondición

lo cual es lógicamente equivalente a: precondición_original o sino nueva_precondición.
En el ejemplo, la nueva precondición se leería como:

p.peso<=35 or p.peso<=50

El caso de las poscondiciones es diferente. Es importante que un subcontratista no exija más que lo establecido en el contrato original, pero no importaría que diera un servicio mayor. En el ejemplo de la entrega, la nueva subclase podría realizar la entrega en menos tiempo lo cual no perjudica, pero si sería perjudicial que la realizara en más tiempo. Esto lleva a la regla para redefinir poscondiciones:

  • La nueva rutina debe mantener la poscondición original o hacerla más fuerte

Ello en el lenguaje Eiffel se escribe:

ensure then nueva_postcondición

lo cual es lógicamente equivalente a: poscondición_original y nueva_poscondición.

Por ejemplo, es posible en la rutina enviar de la clase GREEN_DELIVERY_AUX, redefinir la poscondición como se ve en la figura:

 

En la nueva poscondición se asegura una entrega en un cuarto de hora y además que el paquete llega seco. Se leería de la siguiente manera:

(Result<= now + 0.5) and (p.estado=”seco” and Result<=now +0.25)

La semántica de require then y ensure else evita que se puedan escribir precondiciones más fuertes y poscondiciones más débiles.

Las reglas que rigen los contratos en la herencia, posibilitan la construcción de clases abstractas con fuertes restricciones semánticas para sus subclases. Si se está construyendo una clase que posteriormente puede tener subclases, algunas veces es necesario garantizar las poscondiciones mediante un mecanismo que se llama guarda. Al relajar las precondiciones, puede llegarse a un estado en el cual las poscondiciones son difíciles o imposibles de cumplir.En el caso anterior, si la


 
clase GREEN_DELIVERY_AUX redefine la rutina entregar, permitiendo paquetes de hasta 50 kilogramos pero se establece que los paquetes que pesan más de 35 Kilogramos, pueden entregarse con hasta una hora de demora, la poscondición se escribiría así:

ensure then Result <= new + 1

que indicaría Result <= new + 0.5 and Result <= new + 1. Esto nunca sería posible para paquetes que demoren más de media hora. El mecanismo de guarda implica que al diseñar la clase original se preserven las poscondiciones para futuras subclases. Las poscondiciones preservadas (guarded) se escriben según el siguiente esquema:

(old precondición) implies postcondición

La palabra implies (implica) está indicando que cuando la parte izquierda de la aserción (old precondición) es satisfecha entonces la parte derecha es verdadera. En caso de que la parte izquierda no sea satisfecha no importa lo que pase con la parte derecha (equivalente a la implicación de la lógica de predicados). Para el ejemplo, una poscondición preservada sería escrita en la clase GREEN_DELIVERY como sigue:

ensure
( old p.peso <= 35) implies Result <= new + 0.5

De esta forma, al redefinir la poscondición como

ensure then Result <= new + 1

se está indicando:

( ( old p.peso <= 35) implies Result <= new + 0.5) and then (Result <= new + 1)

La nueva poscondición es justamente lo que se quería expresar: para los paquetes menores a 35 kilogramos se mantiene el contrato y para los mayores (que no estaban en el contrato original) se establece un tiempo de entrega de hasta una hora.
Para casos excepcionales pueden utilizarse precondiciones abstractas sin especificar completamente la aserción. (Meyer,1997)


D.R. © Coordinación de Publicaciones Digitales. DGSCA-UNAM
<