Capítulo 6.
Funciones virtuales: polimorfismo.
En esta ocasión vamos abordar un concepto muy importante de la programación orientada a objetos: el polimorfismo. Esta característica de la POO, permite que podamos construirnos métodos para nuestras clase derivadas que parten de una misma clase base, para que adopten comportamientos totalmente distintos. Es un concepto realmente potente y que se lleva a cabo mediante la utilización de funciones virtuales. Nosotros ya las hemos utilizado. Si te acuerdas, en el capítulo anterior declarábamos la clase que redefiníamos como virtual. ¡Hemos utilizado el polimorfismo y sin enterarnos!.
Una función virtual es un mecanismo que permite a clases derivadas redefinir a las funciones de las clases base. Por tanto, hasta ahora, nos debemos de quedar con que el polimorfismo es una acción que se puede implementar en clases distintas pero que tienen en común el hecho de heredar de una clase base común. Dicho método, pese a ser común para los objetos derivados, es tratado de forma distinta y, por tanto, dando resultados también diferentes dependiendo de con qué clase lo invoquemos. ¿Cómo conseguirlo?. Para poder implementar el polimorfismo tenemos las funciones virtuales. Las funciones virtuales se definen en la clase base y son las que serán redefinidas, luego, en las clases derivadas.
La declaración de una función virtual se consigue mediante la palabra clave virtual precediendo a la declaración de la función. Por ejemplo:
virtual void PonInformacion(void);
En este caso, hemos definido una función virtual llamada PonInformacion que pertenece a una clase base y que podrá ser redefinida, completamente, en una clase derivada para que actúe de forma totalmente distinta a cómo lo hace en la clase base. Es muy importante que la función de la clase base que vamos a redefinir lleve el identificador virtual pues de lo contrario, la cosa no funcionará como es de esperar. Veremos estos problemas más adelante.
A continuación vamos a poner un ejemplo en el que, utilizando la función de arriba, vamos a ver cómo podemos hacer para que una clase que herede dicha función y produzca resultados totalmente distintos a los que se han definido en la clase base para ella. Así mismo, utilizamos new y delete para refrescar la memoria (y nunca mejor dicho :-). Recordad que con new y delete podemos crear objetos (o variables) dinámicamente y de forma muy sencilla.
#include <iostream.h>
class ClaseBase
{
// Esta es la clase base.
// Definimos una función de miembro para la clase base
// que además es virtual e "inline"
public:
vitual void PonInformacion(void) { cout << "\n¡Hola!"; }
};
class ClaseDerivada : public ClaseBase
{
// Hemos definido una clase derivada de la clase ClaseBase.
// Esta clase, va a utilizar el concepto de polimorfismo
// redefiniendo por completo la función PonInformacion
// que hereda de la clase ClaseBase. También es "inline".
public:
void PonInformacion(void) { cout << "\n¡Adios!"; }
};
int main(void)
{
ClaseBase *base = new ClaseBase;
ClaseDerivada *derivada = new ClaseDerivada;
// Ahora llamamos a los métodos de cada una para observar
// que el resultado es distinto.
base->PonInformacion();
derivada->PonInformacion();
delete base;
delete derivada;
return 0;
}
Bueno, el resultado está claro, ¿no?. Mientras que la clase declarada como ClaseBase e instanciada con base, pone en pantalla "¡Hola!", la clase derivada, que ha redefinido la función virtual PonInformacion, pone "¡Adiós!". Es un ejemplo, en definitiva, muy sencillo en el que se puede ver cómo hacer que una función ya definida en una clase base cambie totalmente según nuestras necesidades: es el polimorfismo.
Otra de las cosas que vimos en el capítulo anterior era el hecho de utilizar dentro de la función de la clase derivada, a la función virtual de la clase base. Si nosotros hubiéramos implementado así a la función virtual de la clase derivada:
void PonInformacion(void)
{
// Suponemos que la declaración es inline ya que si no fuera
// inline deberíamos de poner
// void ClaseDerivada::PonInformacion(void)
ClaseBase::PonInformacion();
cout << "\n¡Adios!";
}
Cuando llamáramos a la función PonInformacion() por medio de la clase derivada, esto es, cuando hiciéramos:
derivada->PonInformacion();
Lo que nos saldría por pantalla sería:
¡Hola!
¡Adios!
En contraposición a lo que nos sale en el programa original en el que no llamamos a la función de la clase base, es decir, en el ejemplo original saldría nada más ¡Adiós!. Esto es así porque dentro del cuerpo PonInformacion() que está implementado en la clase ClaseDerivada, llamamos antes de nada a la función de la clase base ClaseBase que se trata como una función heredada más.
Aclarando todo un poco
Si recuerdas el capítulo anterior del curso, habrás observado que, en cierta forma, ya hemos utilizado el polimorfismo, es decir, en el anterior capítulo redefiníamos funciones miembro de una clase base utilizando sus características comunes a la clase derivada en la que trabajábamos pero añadiendo una serie de especificaciones extra para que "además se hiciera otra cosa".
Hasta ahora hemos tratado funciones que son virtuales simples. Recordemos que una función virtual es aquella que, habiéndose declarado en una clase base, vuelve a declararse y a implementarse en una clase derivada, es decir, se redefine en la clase derivada. De esta forma, cuando el objeto instanciado a una clase derivada, llama a esa función virtual, lo que se hace es llamar a la función de la clase derivada no a la función de la clase base (a no ser que, dentro de la función de la clase derivada, se llame a la función de la clase base).
El ejemplo clásico de utilización de funciones virtuales está en el de crear una clase base llamada Forma con una función de miembro para dibujar y de nombre Dibujar. Si nosotros creamos dos clases derivadas de la clase base Forma llamadas Circulo y Rectangulo, respectivamente, y redefinimos la función virtual Dibujar en cada una de las clases base, cuando la llamemos desde cada una de las instancias a la función Dibujar, el compilador sabrá a qué función llamar.