
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
|