Anterior | Superior | Siguiente

Guía de referencia básica de Ada 95

Paquetes.

Definición y uso.

Un paquete (package) es una unidad que agrupa un conjunto de entidades relacionadas. Su aplicación más corriente es para realizar la declaración de un tipo de datos junto con las operaciones primitivas para su manejo, de forma que éstas puedan ser usadas desde el exterior pero se mantengan ocultos sus detalles de funcionamiento. En otras palabras, un paquete es un mecanismo de encapsulamiento y ocultación de información especialmente útil para definir tipos abstractos de datos. Un paquete se divide generalmente en dos partes: especificación e implementación que se sitúan en ficheros diferentes con las extensiones respectivas "*.ads" y "*.adb".

Especificación.

La estructura de la especificación de un paquete es la siguiente:

package <nombre_de_paquete> is
    <elementos públicos>
private
    <elementos privados>
end <nombre_de_paquete>;

La parte anterior a la palabra "private" constituye la interfaz de los servicios ofertados por el paquete (son accesibles a quien lo use). La parte que sigue constituye la parte privada de la especificación, tales como la estructura de datos con que se implementa un tipo ofrecido en la intefaz u otros elementos similares. La palabra "private" puede omitirse, en cuyo caso se entiende que la parte privada de la especificación es nula. La siguiente especificación es de un paquete llamado "Pila_De_enteros" que ofrece un tipo llamado "Pila" y cuatro operaciones primitivas para manejar pilas de valores de tipo Integer.

package Pila_De_Enteros is
   type Pila is limited private;
   procedure Apilar(P: in out Pila;E: in Integer);
   procedure Desapilar(P: in out Pila);
   function Cima(P: Pila) return Integer;
   function Es_Vacía(P:Pila) return Boolean;
private
   type Nodo is record
      Info: Integer;
      Sig : Pila;
   end record;
   type Pila is access Nodo;
end Pila_De_Enteros;

Implementación.

La especificación de un paquete requiere una implementación si contiene declaraciones que requieran completarse (por ejemplo, la declaración de una función requiere su desarrollo). La estructura de un "package body" es:

package body <nombre_de_paquete> is
   <desarrollo del paquete>
end <nombre_de_paquete>;

Todas las declaraciones hechas en la especificación del paquete se pueden usar en la implementación (body), aunque estén en la parte "private". Las declaraciones hechas en el "package body" sólo se pueden usar en el mismo. A continuación se muestra el "package body" del paquete "Pila_de_enteros":

with Unchecked_Deallocation;
package body Pila_De_Enteros is
   procedure Libera is new Unchecked_Deallocation(Nodo,Pila);
   procedure Apilar(P: in out Pila;E: in Integer) is
   begin
      P := new Nodo'(E,P);
   end Apilar;
   procedure Desapilar(P: in out Pila) is
      Aux: Pila := P;
   begin
      P := P.Sig;
      Libera(Aux);
   end Desapilar;
   function Cima(P: Pila) return Integer is
   begin
      return P.Info;
   end Cima;
   function Es_Vacía(P:Pila) return Boolean is
   begin
      return P = null;
   end Es_Vacía;
end Pila_De_Enteros;

Utilización.

Una vez compiladas la especificación y la implementación de un paquete, se puede hacer uso del mismo en otras unidades, simplemente incluyendo el nombre del paquete en sus cláusulas de contexto.

with Text_Io,
     Ada.Integer_Text_Io,
     Pila_De_Enteros;
use  Text_Io,
     Ada.Integer_Text_Io,
     Pila_De_Enteros;
procedure Prueba is
   P: Pila;
begin
   for I in 1..5 loop
      Apilar(P,I);
   end loop;
   while not Es_Vacía(P) loop
      Put(Cima(P));
      New_Line;
      Desapilar(P);
   end loop;
end Prueba;

Paquetes genéricos.

Un paquete genérico es el que posee parámetros que, al ser fijados, permite crear instancias no genéricas aplicables en situaciones diferentes, reutilizando el código sin tener que reescribirlo. Por ejemplo, en el caso de las pilas de enteros del ejemplo previo, es evidente que tanto la estructura de datos del tipo Pila, como los algoritmos de manipulación que la acompañan no dependen en realidad de que los elementos apilados sean de tipo Integer. En consecuencia, se puede escribir un paquete genérico parametrizando el tipo de los elementos, de forma que luego se puedan crear instancias de pilas que acomoden distintos tipos de elementos.

Especificación: se añade la lista de parámetros genéricos formales y se sustituyen todas las apariciones de Integer por T_Elemento, el párametro formal del paquete:

generic
   type T_Elemento is private;
package Pilas is
   type Pila is limited private;
   procedure Apilar(P: in out Pila;E: in T_Elemento);
   procedure Desapilar(P: in out Pila);
   function Cima(P: Pila) return T_Elemento;
   function Es_Vacía(P:Pila) return Boolean;
private
   type Nodo is record
      Info: T_Elemento;
      Sig : Pila;
   end record;
   type Pila is access Nodo;
end Pilas;

Implementación: se sustituyen todas las apariciones de Integer por T_Elemento:

with Unchecked_Deallocation;
package body Pilas is
   procedure Libera is new Unchecked_Deallocation(Nodo,Pila);
   procedure Apilar(P: in out Pila;E: in T_Elemento) is
   begin
      P := new Nodo'(E,P);
   end Apilar;
   procedure Desapilar(P: in out Pila) i s
      Aux: Pila := P;
   begin
      P := P.Sig;
      Libera(Aux);
   end Desapilar;
   function Cima(P: Pila) return T_Elemento is
   begin
      return P.Info;
   end Cima;
   function Es_Vacía(P:Pila) return Boolean is
   begin
      return P = null;
   end Es_Vacía;
end Pilas;

Utilización: el nuevo paquete, "Pilas", aparece en la sentencia with de la cláusula de contexto, pero no así en la use puesto que al ser genérico es sólo una plantilla a partir de la cual definir instancias, pero no es en sí mismo una unidad utilizable directamente. La definición de una instancia se hace especificando un nombre para el paquete no genérico al que dará lugar y los valores reales correspondientes a los parámetros (al menos de aquellos que no tengan un valor por defecto):

with Text_Io,
     Ada.Integer_Text_Io,
     Pilas;
use Text_Io,
    Ada.Integer_Text_Io;
procedure Prueba is
   package Pila_De_Enteros is new Pilas(Integer);
   use Pila_De_Enteros;
   package Pila_De_Reales is new Pilas(Float);
   use Pila_De_Reales;
   P: Pila_De_Enteros.Pila;
   Q: Pila_De_Reales.Pila;
begin
   for I in 1..5 loop
      Apilar(P,I);
   end loop;
   while not Es_Vacía(P) loop
      Put(Cima(P));
      New_Line;
      Desapilar(P);
   end loop;
end Prueba;

Herencia de paquetes.

Se pueden derivar paquetes a partir de otros existentes, creando lo que se llama un paquete "hijo". Normalmente, lo que se pretende conseguir es añadir una nueva funcionalidad sin tener que modificar el paquete original. El siguiente ejemplo crea un paquete hijo de "Pilas" con el fin de proporcionar una operación que permita obtener una copia de una pila. Además, se define una constante para representar la pila vacía que, combinada con la operación de copia, permite vaciar una pila con una sola sentencia:

generic
package Pilas.Copiables is
   Vacía: constant Pila;
   procedure Copia(Pe: in Pila;Ps: out Pila);
private
   Vacía: constant Pila := null;
end Pilas.Copiables;

Un paquete derivado de otro genérico tiene obligatoriamente que ser genérico (aunque no precise parámetros, los precisa su padre). De un paquete no genérico se puede derivar uno genérico.

package body Pilas.Copiables is
   procedure Vaciar(P: in out Pila) is
   begin
      while not Es_Vacía(P) loop
         Desapilar(P);
      end loop;
   end Vaciar;
   function Copia(P:Pila) return Pila is
   begin
      if Es_Vacía(P) then
         return Vacía;
      else
         return new Nodo'(Cima(P),Copia(P.Sig));
      end if;
   end Copia;
   procedure Copia(Pe: in Pila;Ps: out Pila) is
      Aux: Pila := Copia(Pe);
   begin
      Vaciar(Ps);
      Ps := Aux;
   end Copia;
end Pilas.Copiables;

Obsérvese que ni en la especificación ni en la implementación aparece el paquete original en una cláusula de contexto; no es necesario dado que es una extensión del paquete y como tal tiene acceso a todo lo declarado en la especificación del original, incluida la parte privada, aunque no así a lo desarrollado en el "package body" .

Una unidad que requiera toda la funcionalidad de los dos paquetes necesitará incluirlos a ambos.

with Text_Io, Ada.Integer_Text_Io, Pilas, Pilas.Copiables;
use  Text_Io, Ada.Integer_Text_Io;
procedure Prueba is
   package Pila_De_Enteros is new Pilas(Integer);
   use Pila_De_Enteros;
   package Pilas_C is new Pila_De_Enteros.Copiables;
   use Pilas_C;
   P,Q: Pila;
begin
   for I in 1..5 loop
      Apilar(P,I);
   end loop;
   Copia(P,Q);
   Copia(Vacía,P);
   while not Es_Vacía(Q) loop
      Put(Cima(Q));
      New_Line;
      Desapilar(Q);
   end loop;
end Prueba;

Los paquetes derivados y sus ancestros forman una jerarquía que se conoce como familia o sistema de paquetes.

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

Anterior | Superior | Siguiente