Anuncios

Bienvenidos sean a este post, hoy veremos el polimorfismo o tambien conocido como herencia multiple.

Anuncios

En determinadas circunstancias una herencia simple no nos va a resultar suficiente, para comprender mejor este tema vamos a trabajar con clases de animales para este caso utilizaremos una clase llamada caballo, va a tener dos funciones o metodos uno va a ser relinchar y  otro galopar pero vamos a suponer la existencia de una clase llamada pegaso el cual va a ser heredera de caballo y este a su vez va a tener el metodo volar, esto nos va a acarrear un inconveniente porque volar solamente va a ser utilizado por pegaso y nos puede traer una sobrecarga con otra clase, tal vez ave, donde se puede utilizar este metodo pasemos a ver el siguiente ejemplo:

poli00.cpp

# include <iostream>

using namespace std;

class caballo
{
public:
 	void Galopar() { cout << "Galopando\n..."; }
 	virtual void Volar() {
 		cout << "Los caballos no pueden volar.\n";
 		}
private:
 	int suEdad;
};

class pegaso : public caballo
{
public:
 	virtual void Volar() {
 		cout << "Puedo volar! Pruedo volar! Puedo Volar!\n";
 		}
};

const int NumeroCaballos = 5;

int main()
{
 	caballo* Rancho[NumeroCaballos];
 	caballo* apCaballo;
 	int opcion,i;
 	for(i=0; i < NumeroCaballos; i++)
 	{
 		cout << "(1) Caballo (2) Pegaso: ";
 		cin >> opcion;
 		if (opcion == 2)
 			apCaballo = new pegaso;
 		else
 			apCaballo = new caballo;
 		Rancho[i] = apCaballo;
 	}
	cout << endl;
	for(i=0; i < NumeroCaballos; i++)
 	{
 		Rancho[i]->Volar();
 		delete Rancho[i];
 	}
 	return 0;
}
Anuncios

En este ejemplo, vemos las dos clases de estudio (caballo y pegaso), en caballo vamos a tener un metodo llamado volar, donde nos va a aclarar que los caballos no vuelan pero en la clase heredada vamos a redefinir este metodo para informar el hecho de poder volar, luego tenemos dos ciclos uno donde vamos a ingresar el tipo de animal (uno para caballo y dos para pegaso) donde una vez informado el tipo de animal (clase) creara el apuntador necesario y lo almacenara dentro del array Rancho una vez finalizado el ciclo pasaremos al siguiente donde llamara al metodo correcto de cada clase en base al apuntador creado y almacenado en rancho devolviendonos en pantalla el metodo necesitado, la salida del programa es asi:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./poli00
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 2
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 2

Los caballos no pueden volar.
Puedo volar! Pruedo volar! Puedo Volar!
Los caballos no pueden volar.
Los caballos no pueden volar.
Puedo volar! Pruedo volar! Puedo Volar!
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Observemos el resultado final, efectivamente funciono para nuestra necesidad con el inconveniente de tener definido en la clase base una funcion que no corresponde con la misma, en este ejemplo simple no nos trae ningun inconveniente pero supongamos de seguir con esta practica con muchas funciones para distintas clases esto nos podria generar una clase base grande y dificil de depurar, lo ideal es evitar la filtracion ascedente, es decir evitar cargar en exceso a la clase base de sus programas. Para evitar el inconveniente anterior, dentro de la herencia simple, se puede utilizar lo llamado Conversion descendente, veamoslo a traves de un ejemplo:

poli01.cpp

# include <iostream>

using namespace std;

enum TIPO { CABALLO, PEGASO };

class Caballo
{
public:
 	virtual void Galopar() {
 		cout << "Galopando...\n";
 		}
private:
 	int suEdad;
};

class Pegaso : public Caballo
{
public:
 	virtual void Volar() {
 		cout << "Puedo Volar! Puedo Volar! Puedo Volar!\n";
 		}
};

const int NumeroCaballos = 5;

int main()
{
 	Caballo *Rancho[NumeroCaballos];
 	Caballo *apCaballo;
 	int opcion,i;
 	for(i=0; i < NumeroCaballos; i++)
 	{
 		cout << "(1) Caballo (2) Pegaso: ";
 		cin >> opcion;
 		if (opcion == 2)
 			apCaballo = new Pegaso;
 		else
 			apCaballo = new Caballo;
 		Rancho[i] = apCaballo;
 	}
	cout << "\n";
 	for(i=0;i<NumeroCaballos;i++)
 	{
 		Pegaso *apPeg = dynamic_cast < Pegaso * > (Rancho[i]);
 		if (apPeg)
 			apPeg->Volar();
 		else
 			cout << "Solo es un caballo\n";
 		delete Rancho[i];
 	}
 	return 0;
}
Anuncios

En el ejemplo mostrado, podemos observar que el metodo Volar desaparecio de la clase base y esta declarado y definido dentro de la clase Pegaso, despues en el cuerpo del programa donde sustancialmente es similar al anterior, vamos a procesar de nuevo la informacion en la cual el usuario debera ingresar el tipo de caballo y el programa generara un apuntador dependiendo de la clase elegida y todo almacenado dentro del array Rancho. La sutil diferencia esta en el segundo ciclo, aqui vamos a tener una nueva funcion llamada dynamic_cast, esta va a ser utilizado para verificar hacia donde esta apuntando, se genera un nuevo apuntador el cual a traves de dynamic_cast lo va a crear solamente si dentro de la posicion del array informada es de la clase Pegaso, si es verdad (como se verifica en el if) procede a llamar el metodo Volar dentro de la clase pegaso sino muestra un texto en pantalla, la salida es asi:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./poli01
(1) Caballo (2) Pegaso: 2
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 1
(1) Caballo (2) Pegaso: 2
(1) Caballo (2) Pegaso: 1

Puedo Volar! Puedo Volar! Puedo Volar!
Solo es un caballo
Solo es un caballo
Puedo Volar! Puedo Volar! Puedo Volar!
Solo es un caballo
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Como se observa, efectivamente funciona pero vamos a analizar el inconveniente, el metodo encargado de verificar el apuntamiento es uno ahora aceptado e implementado en los compiladores de C++  actuales (posteriores a la version 2.7.2) pero en compiladores antiguos esto podria no funcionar, y si bien como en el caso anterior en programas simples no nos trae inconvenientes para programas mas complejas puede traer algunos inconvenientes de depuracion importantes, este metodo es comunmente denominado RTTI (Identificacion de Tipo en Tiempo de Ejecucion), en caso de falla durante la compilacion puede probar de utilizar la opcion de -frtti o en todo caso verificar con la ayuda del compilador (en gral. con el switch -?, /? o –help) si soporta o no el switch anterior, para ir terminando con estos dos ejemplos, ahora suponiendo la existencia de una clase llamada Ave y va a compartir Volar con Pegaso, en ese caso podriamos considerar una nueva clase llamada Animal, la cual puede abarcar a las tres clases mencionadas (Caballo, Pegaso y Ave) en donde va a tener las funciones de todos ellos y despues vamos heredando las mismas a medida que las vamos creando (filtracion ascedente) pero C++  ofrece una alternativa mejor esta es llamado Herencia multiple pasemos a verlo a traves del siguiente ejemplo:

poli02.cpp

# include <iostream>

using namespace std;

class Caballo
{
public:
 	Caballo() { cout << "Constructor de Caballo..."; }
 	virtual ~Caballo() { cout << "Destrucor de Caballo..."; }
 	virtual void Relinchar() const { cout << "Yihii!"; }
private:
 	int suEdad;
};

class Ave
{
public:
 	Ave() { cout << "Constructor de Ave..."; }
 	virtual ~Ave(){ cout << "Destructor de Ave..."; }
 	virtual void Gorjear() const { cout << "Griii..."; }
 	virtual void Volar() const {
 		cout << "Puedo Volar! Puedo Volar! Puedo Volar!";
 		}
private:
 	int suPeso;
};

class Pegaso: public Caballo, public Ave
{
public:
 	void Gorjear() const { Relinchar(); }
 	Pegaso(){ cout << "Constructor de Pegaso..."; }
 	~Pegaso() { cout << "Destrucor de Pegaso..."; }
};

const int NumeroMagico = 2;

int main()
{
 	Caballo *Rancho[NumeroMagico];
 	Ave *Pajarera[NumeroMagico];
 	Caballo *apCaballo;
 	Ave *apAve;
 	int opcion,i;
 	for(i=0; i < NumeroMagico; i++)
 	{
 		cout << "\n(1) Caballo (2) Pegaso: ";
 		cin >> opcion;
 		if (opcion == 2)
 			apCaballo = new Pegaso;
 		else
 			apCaballo = new Caballo;
 		Rancho[i] = apCaballo;
 	}
	for(i=0; i < NumeroMagico; i++)
 	{
 		cout << "\n(1) Ave (2) Pegaso: ";
 		cin >> opcion;
 		if (opcion == 2)
 			apAve = new Pegaso;
 		else
 			apAve = new Ave;
 		Pajarera[i] = apAve;
 	}
	cout << endl;
	for(i=0; i < NumeroMagico; i++)
 	{
 		cout << "\nRancho[" << i << "]: ";
 		Rancho[i]->Relinchar();
 		delete Rancho[i];
 	}
	for(i=0; i < NumeroMagico; i++)
 	{
 		cout << "\nPajarera[" << i << "]: ";
 		Pajarera[i]->Gorjear();
 		Pajarera[i]->Volar();
 		delete Pajarera[i];
 	}
 	cout << endl;
 	return 0;
}
Anuncios

Analicemos el ejemplo, en este caso tenemos tres clases (Caballo, Ave y Pegaso) las cuales dos van a ser base, Caballo y Ave, para luego crear la clase heredera (Pegaso) la cual va a tener metodos de las dos clases base, como se ve en la linea de creacion de la clase se enlistan con la coma(,) en este caso hereda de Caballo y Ave y lo unico diferente es la redefinicion de Gorjear() donde va a ser reemplazada por Relinchar() la cual viene heredada de Caballo. Despues sustancialmente es lo visto hasta ahora, con ciclos donde se pide ingresar el tipo pero la unica diferencia ahora es la aparicion de un array llamado Pajarera (Rancho todavia continua) en el cual vamos a tener a Pegaso o Ave y dos ciclos mas para, uno para completar la pajarera y otro para mostrarla, veamos la salida:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./poli02
(1) Caballo (2) Pegaso: 1
Constructor de Caballo…
(1) Caballo (2) Pegaso: 2
Constructor de Caballo…Constructor de Ave…Constructor de Pegaso…
(1) Ave (2) Pegaso: 2
Constructor de Caballo…Constructor de Ave…Constructor de Pegaso…
(1) Ave (2) Pegaso: 1
Constructor de Ave…

Rancho[0]: Yihii!Destrucor de Caballo…
Rancho[1]: Yihii!Destrucor de Pegaso…Destructor de Ave…Destrucor de Caballo…
Pajarera[0]: Yihii!Puedo Volar! Puedo Volar! Puedo Volar!Destrucor de Pegaso…Destructor de Ave…Destrucor de Caballo…
Pajarera[1]: Griii…Puedo Volar! Puedo Volar! Puedo Volar!Destructor de Ave…
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Observen como en Rancho en ambos casos, tanto de Caballo como de Pegaso, cuando se invoca a Relinchar esto lo hacen correctamente pero vemos los creadores de caballo y ave para Pegaso y sus respectivos destructores. Pasemos al caso de la pajarera, vamos a llamar a Gorjear y Volar, en el caso de Pegaso es reemplazado por Relinchar y tambien tiene heredado volar, en el caso de Ave funciona con los metodos propios, hasta aqui la explicacion de nuestro primer ejemplo de herencia multiple, como crearla y como se utiliza, veamos el siguiente ejemplo para estudiar constructores en objetos con herencia multiple:

poli03.cpp

# include <iostream>

using namespace std;

typedef int CUARTAS;

enum COLOR { Rojo, Verde, Azul, Amarillo, Blanco, Negro, Cafe };

class Caballo
{
public:
 	Caballo(COLOR color, CUARTAS altura);
 	virtual ~Caballo(){ cout << "Destructor de Caballo...\n"; }
 	virtual void Relinchar() const {
 		cout << "Yihii!...";
 		}
 	virtual CUARTAS ObtenerAltura() const {
 		return suAltura;
 		}
 	virtual COLOR ObtenerColor() const {
 		return suColor;
 		}
private:
 	CUARTAS suAltura;
 	COLOR suColor;
};

Caballo::Caballo(COLOR color, CUARTAS altura):
 	suColor(color),suAltura(altura)
 	{
 		cout << "Constructor de Caballo...\n";
 	}

class Ave
{
public:
 	Ave(COLOR color, bool emigra);
 	virtual ~Ave(){ cout << "Destructor de Ave...\n"; }
 	virtual void Gorjear() const {
 		cout << "Griii...";
 		}
 	virtual void Volar() const {
 		cout << "Puedo Volar! Puedo Volar! Puedo Volar!";
 		}
 	virtual COLOR ObtenerColor() const {
 		return suColor;
 		}
 	virtual bool ObtenerMigracion() const {
 		return suMigracion;
 		}
private:
 	COLOR suColor;
 	bool suMigracion;
};

Ave::Ave(COLOR color, bool emigra):
 	suColor(color), suMigracion(emigra)
 	{
 		cout << "Constructor de Ave...\n";
 	}

class Pegaso : public Caballo, public Ave
{
public:
 	void Gorjear() const { Relinchar(); }
 	Pegaso(COLOR, CUARTAS, bool, long);
 	~Pegaso(){ cout << "Destructor de Pegaso...\n"; }
 	virtual long ObtenerNumeroCreyentes() const {
		return suNumeroCreyentes;
 		}
private:
 	long suNumeroCreyentes;
};

Pegaso::Pegaso(
 	COLOR aColor,
 	CUARTAS altura,
 	bool emigra,
 	long NumCreyen):
 	Caballo(aColor, altura),
 	Ave(aColor, emigra),
 	suNumeroCreyentes(NumCreyen)
 	{
 		cout << "Constructor de Pegaso...\n";
 	}

int main()
{
 	Pegaso *apPeg=new Pegaso(Rojo, 5, true, 10);
 	apPeg->Volar();
 	apPeg->Relinchar();
 	cout << "\nSu Pegaso mide " << apPeg->ObtenerAltura();
 	cout << " cuartas de altura y ";
 	if (apPeg->ObtenerMigracion())
 		cout << " si emigra.";
 	else
 		cout << " no emigra.";
 	cout << "\nUn total de " << apPeg->ObtenerNumeroCreyentes();
 	cout << " personas creen que si existe.\n";
 	delete apPeg;
 	return 0;
}
Anuncios

En este ejemplo, vemos de nuevo las tres clases (Caballo, Ave, Pegaso) pero en este caso vamos a iniciar los constructores con parametros, para caballo vamos a utilizar los parametros altura y color, porque va a ser una forma de distinguirse entre ellos, y por el lado de Ave vamos a tener los parametros color y migracion, aqui color va a ser utilizado para diferenciarlos por el color de sus plumas, definiremos los constructores de las dos clases con la inicializacionn de los valores privados a traves de los parametros predeterminados, les recomiendo este post para entender un poco mas, por esto cuando declaremos a Pegaso va a llevar todos los parametros heredados (altura, color y migracion) y uno extra, suNumeroCreyentes. Cuando definimos el constructor de pegaso, tenemos todos esos parametros inicializados, despues en el main creamos un apuntador a Pegaso donde pasamos todos los parametros necesarios, dando como salida esto:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./poli03
Constructor de Caballo…
Constructor de Ave…
Constructor de Pegaso…
Puedo Volar! Puedo Volar! Puedo Volar!Yihii!…
Su Pegaso mide 5 cuartas de altura y  si emigra.
Un total de 10 personas creen que si existe.
Destructor de Pegaso…
Destructor de Ave…
Destructor de Caballo…
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

Cuando iniciamos con los parametros el apuntador, se ve la asignacion perfectamente realizada pero si vemos con detalle, nos falta un parametro este es el de color, si observaron tienen el mismo metodo y la misma variable, si nosotros hubieramos querido obtener el color de pegaso se deberia haber agregado la siguiente linea de ejemplo:

COLOR colorActual = apPeg->ObtenerColor(); 

Y al compilarlo nos hubiera devuelto el siguiente error:

error: request for member ‘ObtenerColor’ is ambiguous
Anuncios

Para evitar esto deberiamos haber definido de cual clase debe heredarlo, para solucionarlo deberia modificar la linea anterior de esta manera:

COLOR colorActual = apPeg->Caballo:ObtenerColor();

Esto es en el caso de desear que el metodo heredado de caballo devuelva el color, tambien puede utilizar a ave simplemente reemplazando a Caballo por Ave, tambien se podria hacer una redefinicion en la clase Pegaso a traves de la siguiente linea:

virtual COLOR ObtenerColor() const { return Caballo::ObtenerColor(); }
Anuncios

Esto deben tenerlo en cuenta para cuando tengan metodos de formas similares las cuales puedan dar como resultado una ambigüedad para el compilador, en nuestro siguiente ejemplo veremos un caso de herencia multiple compartida:

poli04.cpp

# include <iostream>

using namespace std;

typedef int CUARTAS;
enum COLOR { Rojo, Verde, Azul, Amarillo, Blanco, Negro, Cafe };

class Animal
{
public:
 	Animal(int);
 	virtual ~Animal(){ cout << "Destructor de Animal...\n"; }
 	virtual int ObtenerEdad() const { return suEdad; }
 	virtual void AsignarEdad(int edad) { suEdad=edad; }
private:
 	int suEdad;
};

Animal::Animal(int edad):
 	suEdad(edad)
 	{
 		cout << "Constructor de Animal...\n";
 	}

class Caballo : public Animal
{
public:
 	Caballo(COLOR color, CUARTAS altura, int edad);
 	virtual ~Caballo() { cout << "Destructor de Caballo...\n"; }
 	virtual void Relinchar() const {
 		cout << "Yihii!... ";
 		}
 	virtual CUARTAS ObtenerAltura() const {
 		return suAltura;
		}
 	virtual COLOR ObtenerColor() const {
 		return suColor;
 		}
protected:
 	CUARTAS suAltura;
 	COLOR suColor;
};

Caballo::Caballo(COLOR color, CUARTAS altura, int edad):
 	Animal(edad),
 	suColor(color), 
	suAltura(altura)
 	{
 		cout << "Constructor de Caballo...\n";
 	}

class Ave : public Animal
{
public:
 	Ave(COLOR color, bool migra, int edad);
 	virtual ~Ave() { cout << "Destrucor de Ave...\n"; }
 	virtual void Gorjear() const { cout << "Griii... "; }
 	virtual void Volar() const {
 		cout << "Puedo Volar! Puedo Volar! Puedo Volar!";
 		}
 	virtual COLOR ObtenerColor() const { return suColor; }
 	virtual bool ObtenerMigracion() const { return suMigracion; }
protected:
 	COLOR suColor;
 	bool suMigracion;
};

Ave::Ave(COLOR color, bool migra, int edad):
 	Animal(edad),
 	suColor(color), 
	suMigracion(migra)
 	{
 		cout << "Construcor de Ave...\n";
 	}

class Pegaso : public Caballo, public Ave
{
public:
 	void Gorjear() const { Relinchar(); }
 	Pegaso(COLOR, CUARTAS, bool, long, int);
 	virtual ~Pegaso() { cout << "Destructor de Pegaso...\n"; }
 	virtual long ObtenerNumeroCreyenttes() const {
 		return suNumeroCreyentes;
 		}
 	virtual COLOR ObtenerColor() const {
 		return Caballo::suColor;
 		}
 	virtual int ObtenerEdad() const {
 		return Caballo::ObtenerEdad();
 		}
private:
 	long suNumeroCreyentes;
};

Pegaso::Pegaso(COLOR aColor, CUARTAS altura, bool emigra,
 		long NumCreyen, int edad):
 	Caballo(aColor, altura, edad),
 	Ave(aColor, emigra, edad),
 	suNumeroCreyentes(NumCreyen)
 	{
 		cout << "Constructor de Pegaso...\n";
 	}

int main()
{
 	Pegaso *apPeg = new Pegaso(Rojo, 5, true, 10, 2);
 	int edad = apPeg->ObtenerEdad();
 	cout << "Este Pegaso tiene " << edad << " años de edad.\n";
 	delete apPeg;
 	return 0;
}
Anuncios

En este ejemplo vamos a agregar una nueva clase base, en este caso Animal la cual va a ser la comun para las otras tres (Caballo, Ave, Pegaso), los dos metodos de la clase base van a ser AsignarEdad y ObtenerEdad, en Caballo junto con los metodos heredados vamos a tener tres metodos (Relinchar, ObtenerAltura, ObtenerColor), pasemos a Ave donde tambien es heredada de Animal y tiene cuatro Metodos (Gorjear, Volar, ObtenerColor, ObtenerMigracion) y por ultimo, tenemos a Pegaso, el cual es heredero de Caballo y Ave, y por ende tambien hereda de Animal, tiene todos los metodos derivados de sus clases superiores, donde redefine Gorjear y la reemplaza por Relinchar, como veniamos haciendo hasta ahora, a su vez vamos a tener un nuevo metodo llamado ObtenerNumeroCreyentes, pero como dijimos anteriormente esta clase tambien hereda de Animal y esto nos podria generar una ambigüedad si llamamos a ObtenerEdad u ObtenerColor por esto en Pegaso volvimos a redefinir ambos metodos, en ambos casos le redefinimos la clase a usar, Caballo, y de ahi utilizaremos tanto la variable suColor como el metodo ObtenerEdad. En este caso todos los constructores van a inicializar los valores de sus metodos, Pegaso va a ser el unico que inicializa cinco parametros pero a su vez va a inicializar el resto de las clases, como dijimos anteriormente al haber redirigido cual metodo nos permite los casos de ambigüedad ahora podremos compilar el programa sin problema, la salida es asi:

tinchicus@dbn001dsk:~/lenguaje/c++$ ./poli04
Constructor de Animal…
Constructor de Caballo…
Constructor de Animal…
Construcor de Ave…
Constructor de Pegaso…
Este Pegaso tiene 2 años de edad.
Destructor de Pegaso…
Destrucor de Ave…
Destructor de Animal…
Destructor de Caballo…
Destructor de Animal…
tinchicus@dbn001dsk:~/lenguaje/c++$
Anuncios

En resumen, hemos visto polimorfismo simple, en este caso hemos comenzado con una herencia simple, despues pasamos a una herencia multiple, hemos visto una posibilidad de ambigüedad que se puede generar entre nuestras herencias y como solucionarlo, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00