AnteriorPosterior

Tema 16b. Programación orientada a objetos (2)

  Curso: Curso de Pascal, por Nacho Cabanes

16.2: Herencia y polimorfismo.

En el apartado anterior del tema vimos una introducción a lo que eran los objetos y a cómo podíamos utilizarlos desde Turbo Pascal. Hoy vamos a ver qué es eso de la herencia y el polimorfismo.

Habíamos definido un objeto "título": un cierto texto que se escribía en unas coordenadas de la pantalla que nosotros fijásemos.

type
 titulo = object
   texto: string;                      { El texto que se escribirá }
   x,y : byte;                                   { En qué posición }

   procedure FijaCoords(nuevoX, nuevoY: byte);          { Pues eso }
   procedure FijaTexto(mensaje: string);                    { Idem }
   procedure Escribe;                          { Lo escribe, claro }
 end;

Funciona, pero hemos tecleado mucho para hacer muy poco.  Si de verdad queremos que se vaya pareciendo a un título, lo menos que deberíamos hacer es poder cambiar el color para que resalte un poco más.

Para conseguirlo, podemos modificar nuestro objeto o crear otro. Supongamos que nos interesa conservar ese tal y como está porque lo hemos usado en muchos programas, etc, etc.

Pues con el Pascal "de toda la vida" la opción que nos queda sería crear otro objeto.  Con los editores de texto que tenemos a nuestro alcance, como los que incluye el Entorno de Desarrollo de TP6 y TP7 esto no es mucho problema porque no hay que teclear demasiado: marcamos un bloque, lo copiamos y modificamos lo que nos interese.

Pero vamos teniendo nuevas versiones de nuestros objetos por ahí desperdigadas.  Si un día descubrimos una orden más rápida o más adecuada que Write para usarla en el procedimiento "escribe", tendremos que buscar cada versión del objeto en cada programa, modificarla, etc...

La herencia nos evita todo esto.  Podemos definir un nuevo objeto partiendo del que ya teníamos.  En nuestro caso, conservaremos la base del objeto "Titulo" pero añadiremos el manejo del color y retocaremos "escribe" para que lo contemple.

El nuevo objeto quedaría:

type
 TituloColor = object( titulo )
   color: byte;                                  { El color, claro }
   procedure FijaColores(pluma, fondo: byte);           { Pues eso }
   procedure Escribe;               { Lo escribe de distinta forma }
 end;

Aunque no lo parezca a primera vista, nuestro objeto sigue teniendo los métodos "FijaCoords" y "FijaTexto".  ¿Donde están?  Pues en la primera línea:

object ( titulo )

quiere decir que es un objeto descendiente de "titulo".  Tendrá todos sus métodos y variables más los nuevos que nosotros indiquemos (en este caso, "color" y "FijaColores").  Además podemos redefinir el comportamiento de algún método, como hemos hecho con Escribe.

Veamos cómo quedaría nuestro programa ampliado

 {--------------------------} 
 {  Ejemplo en Pascal:      } 
 {                          } 
 {    Segundo ejemplo de    } 
 {    Prog. Orientada Obj.  } 
 {    OBJETOS2.PAS          } 
 {                          } 
 {  Este fuente procede de  } 
 {  CUPAS, curso de Pascal  } 
 {  por Nacho Cabanes       } 
 {                          } 
 {  Comprobado con:         } 
 {    - Free Pascal 2.2.0w  }
 {    - Turbo Pascal 7.0    } 
 {--------------------------}
 program Objetos2;                 { Nuestro segundo programa en OOP }
 uses crt;                            { Usaremos "GotoXY" y TextAttr }
 type                                { Aquí definimos nuestro objeto } 
   titulo = object 
     texto: string;                      { El texto que se escribirá } 
     x,y : byte;                                   { En qué posición } 
     procedure FijaCoords(nuevoX, nuevoY: byte);          { Pues eso } 
     procedure FijaTexto(mensaje: string);                    { Idem } 
     procedure Escribe;                          { Lo escribe, claro } 
   end;
  type 
   TituloColor = object( titulo ) 
     color: byte;                                  { El color, claro } 
     procedure FijaColores(pluma, fondo: byte);           { Pues eso } 
     procedure Escribe;               { Lo escribe de distinta forma } 
   end;
 var 
   T1: titulo;                      { Una variable de ese tipo } 
   T2: tituloColor;                 { Y otra del otro ;-)  }
 { --- Desarrollo del objeto Titulo --- }
 procedure titulo.FijaCoords(nuevoX, nuevoY: byte); 
 begin                                 { Definimos el procedimiento: } 
   x := nuevoX;                          { Actualiza las coordenadas } 
   y := nuevoY; 
 end;
 procedure titulo.FijaTexto(mensaje: string); 
 begin                                          { Actualiza el texto } 
   Texto := Mensaje; 
 end;
 procedure titulo.Escribe; 
 begin                                           { Muestra el título } 
   Gotoxy(X,Y); 
   Write(Texto); 
 end;
 { --- Métodos específicos de TituloColor --- }
 procedure tituloColor.FijaColores(pluma,fondo: byte); 
 begin                                 { Definimos el procedimiento: } 
   color := pluma + fondo*16;                   { Actualiza el color } 
 end;
 procedure tituloColor.Escribe; 
 begin                                           { Muestra el título } 
   textAttr := color; 
   Gotoxy(X,Y); 
   Write(Texto); 
 end;
 { -- Cuerpo del programa --}
 begin 
   ClrScr; 
   T1.FijaCoords(37,12); 
   T1.FijaTexto('Hola'); 
   T1.Escribe; 
   T2.FijaCoords(37,13); 
   T2.FijaColores(14,2); 
   T2.FijaTexto('Adiós'); 
   T2.Escribe; 
 end.
 

En el mismo programa, como quien no quiere la cosa ;-) , tenemos un ejemplo de polimorfismo: no es sólo que las variables "texto", "x" e "y" esten definidas en los dos objetos de igual forma y tengan valores diferentes, sino que incluso el método "Escribe" se llama igual pero no actúa de la misma forma.
 

Antes de dar este apartado por "sabido" para pasar a ver qué son los constructores, los destructores, los métodos virtuales, etc... un par de comentarios:
 

  • ¿Verdad que la definición de "Escribe" se parece mucho en "Titulo" y en "TituloColor"?  Hay una parte que es exactamente igual.  ¡Pues vaya asco de herencia si tenemos que volver a copiar partes que son iguales!  X-D No, hay un truquillo en Turbo Pascal 7: tenemos la palabra clave "inherited" (heredado), con la que podríamos hacer:
 
procedure tituloColor.Escribe;
begin
  textAttr := color;
  inherited escribe;
end;

Es decir: cambiamos el color y luego todo es igual que el Escribe que hemos heredado del objeto padre ("titulo").  En otras versiones anteriores de Turbo Pascal (5.5 y 6) no existe la palabra inherited, y deberíamos haber hecho

procedure tituloColor.Escribe;
begin
  textAttr := color;
  titulo.escribe;
end;

que es equivalente.  El inconveniente es que tenemos que recordar el nombre del padre.

Los problemas que puede haber con herencia de este tipo los veremos cuando digamos qué son métodos virtuales...


 
  • Segundo comentario: ¿Qué ocurre si ejecutamos "T1.Escribe" sin haber dado valores a las coordenadas ni al texto?  Cualquier cosa...  }:-)
Me explico: no hemos inicializado las variables, de modo que "x" valdrá lo que hubiera en la posición de memoria que el compilador le ha asignado a esta variable. Si este valor fuera mayor de 80, estaríamos intentando escribir fuera de la pantalla.  Igual con "y", y a saber lo que nos aparecería en "texto"...

¿Cómo lo solucionamos?  Pues por ejemplo creando un procedimiento "inicializar" o similar, que sea lo primero que ejecutemos al usar nuestro objeto.  Por ejemplo:

procedure titulo.init;
begin
  x := 1;
  y := 1;
  texto := '';
end;
[...]

procedure tituloColor.init;
begin
  inherited init;
  color := 0;
end;

[...]

begin
  titulo.Init;
  tituloColor.Init;
  [...]
end.


Antes de dar por terminada esta lección, un comentario sobre OOP en general, no centrado en Pascal: puede que alguien oiga por ahí el término "sobrecarga".  Es un tipo de polimorfismo: sobrecarga es cuando tenemos varios métodos que se llaman igual pero cuyo cuerpo es distinto, y polimorfismo puro sería cuando tenemos un solo método que se aplica a argumentos de distinto tipo.

En C++ se habla incluso de la sobrecarga de operadores: podemos redefinir operadores como "+" (y muchos más) para sumar (en este caso) objetos que hayamos creado, de forma más cómoda y legible:

 matriz3 = matriz1 + matriz2

en vez de hacerlo mediante una función:

matriz3 = suma( matriz1, matriz2 )

Continuará...  :-)

Actualizado el: 08-07-2012 13:55

AnteriorPosterior