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:
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...
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á... :-)