Anterior | Superior | Siguiente

Guía de referencia básica de Ada 95

Soporte para Programación Orientada a Objetos.

Introducción.

La Programación Orientada a Objetos (POO) se caracteriza por hacer énfasis en los objetos que interactúan en la solución de un problema, en vez de en la secuencia de operaciones que hay que realizar para resolverlo, que es lo que hace la Programación Imperativa.

Los objetos integran atributos (datos) y métodos (subprogramas) que manipulan los atributos y definen el "comportamiento" del objeto, entendido como su respuesta a la interacción con otros objetos. Un objeto concreto es una instancia de una clase. La clase es quién define las características (atributos y métodos) de los objetos que son de esa clase. El concepto de clase es en terminología de POO lo que el concepto de tipo es en otras terminologías.

Los objetos reúnen las características de encapsulamiento y ocultación propias de los tipos abstractos de datos que en Ada se plasman en el mecanismo de los paquetes. Además, incluyen dos características nuevas: la herencia y el polimorfismo.

La herencia es un mecanismo por el cual se puede a partir de una clase (clase base) definir otra (clase derivada) que tiene sus mismas características más otras adicionales (con la posibilidad añadida de poder redefinir, para la clase heredera, los métodos que se desee de la clase original).

El polimorfismo propio de la POO es el polimorfismo de clases que supone que un objeto concreto de una clase derivada es, al mismo tiempo, una forma alternativa de objeto de la clase base, de manera que si en una operación se especifica el uso de un objeto de un tipo polimórfico de la clase base, en realidad se podrá usar un objeto de cualquier clase derivada de la misma y, si en el transcurso de dicha operación se invoca a un método de la clase base que ha sido redefinido por sus derivadas, en realidad se invocará al que corresponda según la clase concreta del objeto que se esté utilizando en ese momento. Si en el momento de la compilación de un programa se conoce la clase concreta del objeto que se usará, las llamadas a sus métodos quedarán fijadas mediante lo que se conoce como "enlace estático" (static binding), si no, habrá de determinarse la llamada adecuada en tiempo de ejecución, efectuándose entonces un "enlace dinámico" (late binding). En Ada el enlace dinámico se conoce como dispatching.

Existen otras formas de polimorfismo, como son: el polimorfismo paramétrico y el polimorfismo por sobrecarga.

Tipos "tagged".

Ada 95 proporciona las características OO de herencia y polimorfismo a través de los tipos "tagged". Un tipo "tagged" es un tipo privado o un record en cuya declaración se usa la palabra reservada "tagged".

type T is tagged private;

type T is tagged record
    ...
end record;

Un tipo "tagged" puede ser extendido, dando lugar a otro tipo que se dice derivado de aquel. El tipo del que se deriva se dice que es "padre" del derivado. Del tipo derivado se pueden a su vez derivar otros, formando una jerarquía de tipos (una familia de tipos). Los tipos derivados también son "tagged". Cada objeto "tagged" tiene un atributo llamado "Tag" que identifica su clase concreta.

type T1 is new T with private;

type T1 is new T with record
    ...
end record;

El tipo derivado puede añadir nuevos campos, siempre que no se denominen igual que los de sus ancestros. Si el tipo derivado se declara en el mismo paquete que su padre o en un paquete derivado de aquél, tiene total acceso a los campos de su padre; si se declara en un paquete diferente, sólo tiene el acceso que proporcionen las operaciones primitivas de su padre.

Un tipo derivado no tiene porqué añadir ningún campo.

type T2 is new T with null record;

Asimismo, el tipo primitivo de una jerarquía no tiene por qué tener ningún campo, de forma que sólo existan los añadidos por sus extensiones.

type Otro is tagged null record;

El tipo derivado hereda las operaciones primitivas de su padre, pero puede sustituirlas por sus propias versiones. Además puede añadir nuevas operaciones. Una operación primitiva no puede tener parámetros "tagged" de tipos diferentes.

Se pueden hacer conversiones desde un tipo derivado a un antecesor suyo.

X: T;
Y: T1;
...
X := T(Y);

La conversión de un antecesor a un derivado requiere la inclusión de un agregado con los valores de los campos "extra".

Y := T1(X) with <valores de los campos "extra">;

Polimorfismo.

El polimorfismo de clases se consigue en Ada utilizando un tipo polimórfico explícito. Dado un tipo "tagged", T, T'Class designa un tipo polimórfico al que pertenecen todos los objetos que son de tipo T o de cualquier tipo derivado de T. Si en un subprograma se usa un parámetro formal de tipo T'Class, se podrá usar como parámetro real cualquier objeto del tipo T o de un tipo derivado de T.

El tipo T'Class puede usarse para declarar objetos locales sólo en un contexto en el que el tipo concreto se vaya a conocer en el momento de elaborar la declaración.

procedure P(A: T'Class) is
    B: T'Class := A;
begin
    ...
end;

Si una operación primitiva tiene varios parámetros formales polimórficos, al llamarla, todos los parámetros reales deben ser del mismo tipo concreto.

Tipos y subprogramas "abstract".

Un tipo "abstract" es un tipo "tagged"  diseñado para servir exclusivamente como cabecera de una jerarquía de tipos,  sin que se puedan declarar objetos con ese tipo. Se declara usando la palabra reservada "abstract".

type TA is abstract tagged private;
type TA is abstract tagged record
    ...
end record
;

Un subprograma "abstract" es uno sin cuerpo, declarado usando la palabra reservada "abstract" con la intención de que sea sustituido más tarde por un heredero. Si el subprograma "abstract" es una operación primitiva de un tipo "tagged", éste también debe ser "abstract".

procedure P(...) is abstract;

Cuando se deriva un tipo de un tipo "abstract", si el tipo derivado también es "abstract" (lo que hay que explicitar, ya que la condición de "abstract" no se hereda) el subprograma heredado también es "abstract". Si, por el contrario, el tipo derivado no es "abstract", los subprogramas "abstract" de su padre deben ser sustituidos por versiones no "abstract".

Tipos "Controlled".

Los tipos Controlled y Limited_Controlled, definidos en el paquete Ada.Finalization ofrecen tres operaciones interesantes: Initialize, Adjust —no aplicable a los Limited_Controlled— y Finalize. En principio, estas tres operaciones no hacen nada, pero pueden redefinirse para cada tipo que se derive de Controlled, o, salvo Adjust, de Limited_Controlled. Si se redefinen, deben declararse en la misma zona donde se fija la estructura de representación del tipo y permiten una gestión adecuada de aspectos clave de la implementación.

Lo tipos Controlled y Limited_Controlled de Ada efectúan una llamada automática al procedimiento Finalize cuando una variable alcanza el final de su tiempo de vida. Un programador puede sobrecargar el procedimiento Finalize para que destruya las partes de la estructura del tipo que se encuentren en memoria dinámica.

El procedimiento Adjust sirve para resolver los problemas de la asignación. Según establece el "Manual de Referencia de Ada", cuando se realiza una asignación, primero se asigna el valor a una variable temporal y se llama a Adjust para esta variable, luego se finaliza la variable destino de la asignación, y, por último, se asigna la variable temporal a la destino y se llama a Adjust para esta última y a Finalize para la temporal.

Puesto que Adjust se llama siempre automáticamente después de una asignación bit a bit, puede emplearse para realizar una copia separada de los datos que se encuentren en memoria dinámica. El uso de la variable temporal en el proceso es para prever el caso de que una variable se asigne a sí misma —la finalización del destino destruye la fuente—, pero los compiladores pueden hacer optimizaciones que incluyen no utilizar la variable temporal cuando se asignan variables diferentes, y no hacer nada cuando una variable se asigna a sí misma. Con estas optimizaciones, el peor escenario posible ocurre cuando a una variable se le asigna una modificación de sí misma, único caso en que hay que realizar todos los pasos. Las llamadas automáticas a Finalize aseguran que no queda memoria perdida.

El procedimiento Initialize se llama automáticamente cuando se crea una variable que no tiene inicializaciones por defecto, y sirve para realizar inicializaciones que sean necesarias para empezar a trabajar de una forma segura con la variable.

© Grupo de Estructuras de Datos y Lingüística Computacional - ULPGC.

Anterior | Superior | Siguiente