Fundamentos de programación con Módula-2 (Tema 14_2)

14.4 Realización de tipos abstractos en Modula-2

Tal como se ha indicado, el concepto de módulo está ligado a la idea de abstracción; en particular, los módulos de Modula-2 son un mecanismo muy apropiado para la definición de tipos abstractos de datos.

14.4.1 Definición de tipos abstractos como módulos
Los módulos de Modula-2 permiten definir bien tipos abstractos de datos, gracias a la posibilidad de agrupación de diferentes elementos relacionados entre sí, y a la ocultación de los detalles de realización interna.

Podríamos decir que la interfaz del módulo equivale a la especificación del tipo, abstracto, de datos, y que la realización del módulo establece su representación interna.

Continuando con el ejemplo de los meses del año, lo definiremos como tipo abstracto especificando la colección de valores posibles y la colección de operaciones de manipulación que necesitemos. Fijando de manera un tanto arbitraria estas operaciones de manipulación, podríamos escribir una primera versión de la especificación del tipo abstracto:

(*_/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\_
_/-\_
_/-\_   Este módulo define el tipo abstracto MES
_/-\_
_/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\_
*)

DEFINITION MODULE Meses;

TYPE TipoMes =
   ( MesNulo, Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio,
     Agosto, Septiembre, Octubre, Noviembre, Diciembre );

PROCEDURE Siguiente( mes: TipoMes; n: INTEGER ): TipoMes;
(* Mes que será dentro de ‘n’ meses *)

PROCEDURE Leer( VAR mes: TipoMes );
(* Lee el mes, en letra, desde el fichero de entrada *)

PROCEDURE Escribir( mes: TipoMes; ancho: INTEGER );
(* Escribe el mes en letra, con el ancho indicado *)

END Meses.

Teniendo definida la interfaz podemos ya pasar a escribir algún programa que use ese tipo abstracto de datos. Por ejemplo, podemos programar simplemente el cálculo del mes final de un plazo, conociendo el mes inicial y la duración. El programa podría ser:

(*_/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\_
_/-\_
_/-\_   Este programa determina el mes en que termina un plazo,
_/-\_   sabiendo el mes inicial y los mese que dura.
_/-\_
_/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\_
*)

MODULE MesFinal;
   FROM InOut IIMPORT ReadInt, WriteString, WriteLn;
   IMPORT Meses;
   VAR
      ahora, luego: Meses.TipoMes;
      plazo: INTEGER;

BEGIN
   (*– Leer los datos –*)
      LOOP
         WriteString( ‘¿En qué mes estamos? ‘ );
         Meses.Leer( ahora );
         Meses.Escribir( ahora, 10 ); WriteLn;
         IF ahora <> Meses.MesNulo THEN EXIT END;
         WriteString( ‘Mes no reconocido, repita’ ); WriteLn
      END;
      WriteString( ‘¿Cuántos meses faltan? ‘ );
      ReadInt( plazo ); WriteLn;
   (*– Calcular e imprimir el resultado –*)
      luego := Meses.Siguiente( ahora, plazo );
      WriteString( ‘El plazo acaba en ‘ );
      WriteLn
END MesFinal.

Las posibilidades de usar el tipo abstracto con sólo definir la interfaz incluyen la compilación real del programa que acabamos de escribir. No podremos, sin embargo, ejecutarlo hasta haber escrito la realización de tipo abstracto.

A continuación presentamos una posible realización del módulo Meses.

(*_/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\_
_/-\_
_/-\_   Este módulo realiza el tipo abstracto MES
_/-\_
_/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\__/-\_
*)

IMPLEMENTATION MODULE Meses;

FROM InOut IMPORT Read, Write, EOL;
TYPE NombreMes = ARRAY [0..10] OF CHAR;
VAR nombres: ARRAY [MesNulo..Diciembre] OF NombreMes;

PROCEDURE Siguiente( mes: TipoMes; n: INTEGER ); TipoMes;
(* Mes que será dentro de ‘n’ meses *)
   VAR x, m: INTEGER;
BEGIN
   (*– ajusta el incremento a un año, como máximo –*)
      x := n MOD 12;
   (*– incrementa en forma circular –*)
      m := ORD(mes); INC( m, x);
      IF m > 12 THEN DEC( m, 12 ) ENDM
      RETURN VAL( TipoMes, m )
END Siguiente;

PROCEDURE Leer( VAR mes: TipoMes );
(* Lee el mes, en letra, desde el fichero de entrada *)
   VAR c: CHAR;

PROCEDURE Ensayo( c1, c2: CHAR; m1, m2: TipoMes): TipoMes;
(* Distingue entre dos meses por el siguiente carácter *)
BEGIN
   Read( c );
   IF CAP( c ) = c1 THEN RETURN m1
   ELSIF CAP( c ) = c2 THEN RETURN m2
   ELSIF RETURN MesNulo END
END Ensayo;

BEGIN (* Leer *)
   (*– Buscar el comienzo del nombre –*)
      REPEAT Read( c ) UNTIL c<>’ ‘;
   (*– Reconocer los primeros caracteres –*)
      CASE CAP( c ) OF
          ‘A’: mes := Ensayo( ‘B’, ‘G’, Abril, Agosto )
      |   ‘D’: mes := Diciembre
      |   ‘E’: mes := Enero
      |   ‘F’: mes := Febrero
      |   ‘J’: Read( c );
              IF CAP( c ) = ‘U’ THEN
                 mes := Ensayo( ‘L’, ‘N’, Julio, Junio )
              ELSE
                 mes := MesNulo
              END
      |   ‘M’: Read( c );
               IF CAP( c ) = ‘A’ THEN
                  mes := Ensayo( ‘R’, ‘Y’, Marzo, Mayo )
               ELSE
                  mes := MesNulo
               END
      |   ‘N’: mes := Noviembre
      |   ‘O’: mes := Octubre
      |   ‘S’: mes := Septiembre
      ELSE mes := MesNulo END;

   (*– Salar el resto del nombre –*)
      WHILE (CAP( c ) >= ‘A’) AND (CAP( c ) <= ‘Z’) DO
         Read( c )
      END
END Leer;

PROCEDURE Escribir( mes: TipoMes; ancho: INTEGER );
(* Escribe el mes en letra, con el ancho indicado *)
   VAR k: INTEGER;
BEGIN
   FOR k := 0 TO ancho-1 DO
      IF k < 10 THEN              (* escribir letras del nombre *)
         Write( nombre[ mes, k ] )
      ELSE                            (* rellenar con blancos *)
         Write( ‘ ‘ )
      END
   END
END Escribir;

BEGIN (* Meses *)
   nombres[ MesNulo    ] := ‘??????????’;
   nombres[ Enero      ] := ‘Enero     ‘;
   nombres[ Febrero    ] := ‘Febrero   ‘;
   nombres[ Marzo      ] := ‘Marzo     ‘;
   nombres[ Abril      ] := ‘Abril     ‘;
   nombres[ Mayo       ] := ‘Mayo      ‘;
   nombres[ Junio      ] := ‘Junio     ‘;
   nombres[ Julio      ] := ‘Julio     ‘;
   nombres[ Agosto     ] := ‘Agosto    ‘;
   nombres[ Septiembre     ] := ‘Septiembre’;
   nombres[ Octubre    ] := ‘Octubre   ‘;
   nombres[ Noviembre  ] := ‘Noviembre ‘;
   nombres[ Diciembre  ] := ‘Diciembre ‘;
END Meses.

Una vez compilados el programa principal y el módulo, podemos ya montar y ejecutar el programa completo, y obtener, por ejemplo:

 

14.4.2 Tipos opacos
El módulo Meses del ejemplo anterior define realmente un tipo abstracto de datos, ya que especifica la colección de valores posibles del TipoMes, y las operaciones que pueden realizarse con esos valores.

Sin embargo no se aplica de manera total la propiedad de ocultación, ya que es visible en la interfaz que el tipo se define como un tipo enumerado, y esto permite usar todas las operaciones posibles del lenguaje para estos tipos. Por ejemplo, nada impide emplear el procedimiento predefinido INC para intentar obtener el mes final del plazo, escribiendo:
                . . . .
                luego := ahora;
                INC( luego, plazo );
                . . . .
Esto sería un error, ya que estas setencias no tienen en cuenta que los meses van avanzando cíclicamente, y que después de Diciembre viene de nuevo Enero. El procedimiento INC no opera cíclicamente, sino que da error si se rebasa el final del año.

Para asegurarse de que a los datos de un tipo abstracto se les aplican únicamente las operaciones que se han definido expresamente para ellos, es posible usar un artificio que permite definir los llamados tipos opacos en Modula-2.

Un tipo opaco se define en un "módulo de definición" escribiendo sólo la parte inicial de una definición de tipo, en la forma:

               TYPE nombre_de_tipo;

Como puede observarse, sólo se especifica el nombre del tipo, pero no se da ninguna información acerca de los posibles valores de este tipo ni de su representación. De esta manera es imposible aplicar ninguna construcción del lenguaje a los valores de ese tipo, salvo las siguientes:

  • Un valor del tipo opaco puede asignarse a una variable del mismo tipo.
  • Los valores o variables de ese tipo pueden pasarse como argumentos.
  • Dos valores del mismo tipo pueden comparase para ver si son iguales.

La definición completa del tipo ha de hacerse en el correspondiente "módulo de implementación". Sin embargo esta definición está sometida a fuertes limitaciones, para permitir que el "módulo de definición" pueda compilarse sin usar ninguna información del "módulo de implementación".

Las versiones más estrictas de Modula-2 exigen que los tipos opacos se definan internamente como tipos puntero, exclusivamente. Otras versiones permiten definir estos tipos con más libertad, pero de modo que el espacio de memoria necesario para almacenar cada valor del tipo sea mayor que el espacio ocupado por un puntero. Esto permite, en general, definir también los tipos opacos como tipos ordinales de cualquier clase.

De todas formas la seguridad conseguida en el uso de la interfaz del módulo es a costa de complicar la invocación de ciertos elementos. Por ejemplo, no es posible definir funciones que devuelvan los valores constantes que se necesiten, o bien que determinen si el valor del tipo opaco es un valor en particular.

En el caso de tipos opacos que deban realizarse como estructuras de datos, será necesario, en general, realizarlos con variables dinámicas, y habrá que incluir sistemáticamente operaciones sobre el tipo abstracto para crear o destruir variables de ese tipo.

14.4.3 Datos encapsulados
Los esquemas anteriores equivalen a definir un tipo (abstracto), del que luego hay que declarar las variables necesarias. Cuando en un programa sólo hay que manejar una única variable de ese tipo, se puede conseguir fácilmente una ocultación total sin necesidad de recurrir a tipos opacos, si dicha variable única es interna al módulo en que se desarrolla el tipo abstracto.

Existe una analogía clara entre esta idea y las dos formas básicas de declaración de variables en Modula-2. En el primer caso escribiríamos, por ejemplo:

              TYPE Nombre = ARRAY [0..20] OF CHAR;
              . . . .
              VAR autor: Nombre;
              . . . .
              ReadString( autor );
              WriteString( autor )

En el segundo caso escribiríamos:

             VAR autor: ARRAY [0..20] OF CHAR;
             . . . .
             ReadString( autor );
             WriteString( autor )

En este segundo caso el tipo es anónimo, y no tiene nombre particular. Aplicando esta idea al manejo de datos abstractos en módulos, el primer caso, descrito en las acciones anteriores, equivaldría a escribir:

DEFINITION MODULE Nombres;
   TYPE Nombre = ARRAY [0..20] OF CHAR;
   PROCEDURE Leer( VAR n: Nombre );
   PROCEDURE Escribir( n: Nombre );
END Nombres.

MODULE Principal;
   IMPORT Nombres;
   VAR autor: Nombres.Nombre;
BEGIN
   Nombres.Leer( autor );
   Nombres.Escribir( autor );
END Principal.

El esquema correspondiente al segundo caso, sin tipo explícito, sería:

DEFINITION MODULE Autor;
   PROCEDURE Leer;
   PROCEDURE Escribir;
END Autor.

MODULE Principal;
BEGIN
   Autor.Leer;
   Autor.Escribir
END Principal.

En este segundo esquema el módulo no corresponde al tipo, sino a la variable, y de acuerdo con ello se ha elegido el nombre del módulo. La variable en sí estaría oculta en el "módulo de implementación", en la forma:

IMPLEMENTATION MODULE Autor;
   VAR autor: ARRAY [0..20] OF CHAR;
   . . . .
END Autor.

El módulo constituye una cápsula que protege al dato, de manera que sólo se puede acceder a él usando las operaciones de la interfaz, y ninguna otra en absoluto. Cuando se usa esta técnica no se pueden aplicar al dato desde el exterior del módulo ni siquiera las operaciones enunciadas para los tipos opacos, pues lo que está encapsulado no es ya el tipo, sino el dato mismo. Obsérvese que las operaciones que manejan el dato no lo mencionan como argumento.

Repetimos que esta técnica resulta fácil de aplicar sólo cuando existe únicamente una variable del tipo abstracto que se desea ocultar.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s