Bienvenidos a este nuevo post, en este continuaremos con lo visto en el post anterior sobre herencia multiple (o polimorfismo), hoy nos dedicaremos mas a tratar sobre la herencia virtual y los datos abstractos y algunas implementaciones mas, empecemos con el siguiente ejemplo para explicar herencia virtual:

# 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 << “Desctructor 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 : virtual public Animal
{
public:
Caballo(COLOR color, CUARTAS altura, int edad);
virtual ~Caballo() { cout << “Destrucor de Caballo…\n”; }
virtual void Relinchar() const { cout << “Yihii…”; }
virtual CUARTAS ObtenerAltura() const
{
return suAltura;
}
virtual COLOR ObtenerColor() const
{
return suColor;
}
protected:
COLOR suColor;
CUARTAS suAltura;
};

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

class Ave : virtual public Animal
{
public:
Ave(COLOR color, bool migra, int edad);
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;
}
protected:
COLOR suColor;
bool suMigracion;
};

Ave::Ave(COLOR color, bool emigra, int edad):
Animal(edad), suColor(color), suMigracion(emigra)
{
cout << “Constructor de Ave…\n”;
}

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

Pegaso::Pegaso(
COLOR aColor,
CUARTAS altura,
bool emigra,
long NumCreyen,
int edad):
Caballo(aColor, altura, edad),
Ave(aColor, emigra, edad),
Animal(edad * 2),
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.” << endl;
delete apPeg;
return 0;
}

Traido del post anterior, en ese caso utilizamos una herencia multiple con una clase base comun (Animal) pero en ese ejemplo la clase base se duplicaba para cada clase heredera, por esto tanto Caballo como Ave tenian su propia copia de Animal, esto nos dejaba latente la posibilidad de generar una ambigüedad con el metodo ObtenerEdad porque estaba en las dos clases y para evitar eso decidimos llamarlo desde la ubicacion deseada (Caballo), si analizamos ahora este ejemplo son muy pocas las diferencias, entre ellas la creacion de las clases Caballo y Ave donde ahora su herencia es virtual dando como resultado que no van a tener mas una copia de Animal en sus derivaciones en cambio van a utilizar la clase comun eliminando la ambigüedad de la edad existente en el ejemplo mencionado al principio de este parrafo. Tambien podemos ver la declaracion de la clase Pegaso y en este caso observamos la herencia donde siguen siendo como siempre pero dentro de la clase el unico metodo redefinido es ObtenerColor, donde informamos de cual clase utilizarlo (Caballo) para evitar una ambigüedad en la compilacion. La salida del programa es asi:

Constructor de Animal…
Constructor de Caballo…
Constructor de Ave…
Constructor de Pegaso…
Este Pegaso tiene 4 años de edad.
Destructor de Pegaso…
Destructor de Ave…
Destrucor de Caballo…
Desctructor de Animal…

En base a la salida puede verse lo implementado en el cuerpo de main donde nosotros guardamos en edad el resultado de ObtenerEdad y pueden observar que el mismo es el metodo implementado en Animal, lo cual nos facilito mucho el hecho de poder evitar ciertos errores en el momento de la compilacion, un ultimo detalle, en la clase  base podemos utilizar el metodo private pero los metodos para los herederos “virtuales” deben ser protected para que la informacion puede fluir en todo el programa y no obtener ningun error en la compilación. Ahora pasemos a ver datos abstractos, este tipo de derivacion o herencia se utiliza para cuando tenemos una clase base donde va a ser redefinida en distintas oportunidades para distintos propositos, veamos el siguiente ejemplo:

# include <iostream>

using namespace std;

class Figura
{
public:
Figura(){}
virtual ~Figura(){}
virtual long ObtenerArea() { return -1; }
virtual long ObtenerPerim() { return -1; }
virtual void Dibujar() {}
private:
};

class Circulo : public Figura
{
public:
Circulo(int radio): suRadio(radio){}
~Circulo(){}
long ObtenerArea() { return 3 * suRadio * suRadio; }
long ObtenerPerim() { return 6 * suRadio; }
void Dibujar();
private:
int suRadio;
int suCircunferencia;
};

void Circulo::Dibujar()
{
cout << “Aqui va la rutina para dibujar un circulo!\n”;
}

class Rectangulo : public Figura
{
public:
Rectangulo(int longitud, int ancho):
suLongitud(longitud), suAncho(ancho){}
virtual ~Rectangulo(){}
virtual long ObtenerArea()
{
return suLongitud * suAncho;
}
virtual long ObtenerPerim()
{
return 2 * suLongitud + 2 * suAncho;
}
virtual int ObtenerLongitud() { return suLongitud; }
virtual int ObtenerAncho() { return suAncho; }
virtual void Dibujar();
private:
int suAncho;
int suLongitud;
};

void Rectangulo :: Dibujar()
{
for (int i=0; i < suLongitud; i++)
{
for (int j=0; j < suAncho; j++)
cout << “x”;

cout << endl;
}
}

class Cuadrado : public Rectangulo
{
public:
Cuadrado(int longitud);
Cuadrado(int longitud, int Ancho);
~Cuadrado(){}
long ObtenerPerim() { return 4 * ObtenerLongitud(); }
};

Cuadrado::Cuadrado(int longitud):
Rectangulo(longitud,longitud) {}

Cuadrado::Cuadrado(int longitud, int ancho):
Rectangulo(longitud,ancho)
{
if (ObtenerLongitud() != ObtenerAncho())
cout << “Error no es un Cuadrado… Un Rectangulo?\n”;
}

int main()
{
int opcion;
bool fSalir = false;
Figura * sp;

while (! fSalir)
{
cout << “(1) Circulo (2) Rectangulo (3) Cuadrado (0) Salir: “;
cin >> opcion;
switch(opcion)
{
case 0: fSalir = true;
break;
case 1: sp = new Circulo(5);
break;
case 2: sp = new Rectangulo(4,6);
break;
case 3: sp = new Cuadrado(5);
break;
default:
cout << “Escriba un valor entre 0 y 3!!!\n”;
continue;
break;
}
if (fSalir) break;
sp->Dibujar();
delete sp;
cout << endl;
}
return 0;
}

Veamos este ejemplo, tenemos una clase base llamada Figura, en la cual vamos a tener los siguientes metodos, ObtenerArea y ObtenerPerim los cuales devuelven un error y un metodo llamado Dibujar el cual no realiza nada, despues declaramos la clase Circulo el cual va a ser heredera de Figura, los metodos derivados van a ser redefinidos dentro de la clase para obtener los datos del circulo y “dibujar” el circulo, luego pasamos a declarar a Rectangulo, este tambien es heredero de Figura y tambien redefinio los metodos derivados dentro de su clase, ahora cuando redefinimos Dibujar esta va ser la encargada de dibujarlo en pantalla, en base a su longitud y ancho, si observaron en las dos clases vistas estas en una redefinimos los metodos como virtuales y en otra no (Rectangulo y Circulo, respectivamente) esto significa que no es obligatorio redefinirlas como virtuales pero si es recomendado como recordatorio incluir el termino virtual, ahora finalmente pasemos a la clase Cuadrado esta es derivada de la clase Rectangulo, en este caso vamos a utilizar una sobrecarga de Constructores de Cuadrado y volvemos a redefinir el metodo ObtenerPerim para la figura que necesitamos, cuando vamos a definir los constructores, el primero con un solo parametro va a llamar a Rectangulo y le va a pasar el parametro de longitud, como es uno solo cuando ejecute el metodo Dibujar va a hacer un cuadrado (que es el objetivo) para el segundo constructor observamos el envio de dos datos distintos (longitud y ancho) a Rectangulo pero como fue llamado de Cuadrado, en esta definicion hacemos un chequeo en caso de ser dos valores distintos devolveremos la respuesta de si en realidad no sera un rectangulo lo deseado. Ahora pasemos al main, en este caso vamos a crear dos variables y un objeto, el destacado es fSalir por estar en estado false porque el permite hacer el bucle, siempre y cuando no entre en true, despues tendremos una pregunta sobre que queremos dibujar, una vez elegida la opcion mediante un switch podremos definir el tipo de objeto en el apuntador definido en el principio, porque era Figura y ahora podremos redefinir el tipo de objeto (clase) a ejecutar, tambien verificara el ingreso de los valores correctos, y la opcion de salir, les muestro la salida del programa:

(1) Circulo (2) Rectangulo (3) Cuadrado (0) Salir: 1
Aqui va la rutina para dibujar un circulo!

(1) Circulo (2) Rectangulo (3) Cuadrado (0) Salir: 2
xxxxxx
xxxxxx
xxxxxx
xxxxxx

(1) Circulo (2) Rectangulo (3) Cuadrado (0) Salir: 3
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx

(1) Circulo (2) Rectangulo (3) Cuadrado (0) Salir: 5
Escriba un valor entre 0 y 3!!!
(1) Circulo (2) Rectangulo (3) Cuadrado (0) Salir: 0

Todo lo visto hasta ahora en este ejemplo, es llamado ADT (Tipo de Dato Abstracto) esto es llamado asi porque la clase base es utilizada solamente como una interfaz para el resto de las clases derivadas de ella, tambien para tener en cuenta que las clases bases del tipo ADT solo son utilizadas como referencias para el resto de las clases derivadas de ellas y nunca debe ser creada una instancia de una ADT. Para evitar este tipo de inconveniente, es decir la creacion de una instancia de una clase ADT, se utilizan las funciones virtuales puras. Las funciones virtuales son una declaracion simple dentro de la clase ADT, es decir a cada uno de los metodos declarados dentro de la clase se le agrega un igual a cero (=0) y el compilador de C++ entiende que las funciones esas son virtuales puras, esto nos obligara a volver a redefinirlas en las clases herederas de la clase base ADT sino nosotros no hicieramos eso la clase derivada se convertiria en ADT tambien, les vuelvo a pasar el ejemplo anterior pero ahora modificado para la visualizacion de las fuciones virtuales puras:

# include <iostream>

using namespace std;

class Figura
{
public:
Figura(){}
virtual ~Figura(){}
virtual long ObtenerArea() = 0;
virtual long ObtenerPerim() = 0;
virtual void Dibujar() = 0;
private:
};

class Circulo : public Figura
{
public:
Circulo(int radio): suRadio(radio){}
~Circulo(){}
long ObtenerArea() { return 3 * suRadio * suRadio; }
long ObtenerPerim() { return 6 * suRadio; }
void Dibujar();
private:
int suRadio;
int suCircunferencia;
};

void Circulo::Dibujar()
{
cout << “Aqui va la rutina para dibujar un circulo!\n”;
}

class Rectangulo : public Figura
{
public:
Rectangulo(int longitud, int ancho):
suLongitud(longitud), suAncho(ancho){}
virtual ~Rectangulo(){}
virtual long ObtenerArea()
{
return suLongitud * suAncho;
}
virtual long ObtenerPerim()
{
return 2 * suLongitud + 2 * suAncho;
}
virtual int ObtenerLongitud() { return suLongitud; }
virtual int ObtenerAncho() { return suAncho; }
virtual void Dibujar();
private:
int suAncho;
int suLongitud;
};

void Rectangulo :: Dibujar()
{
for (int i=0; i < suLongitud; i++)
{
for (int j=0; j < suAncho; j++)
cout << “x”;

cout << endl;
}
}

class Cuadrado : public Rectangulo
{
public:
Cuadrado(int longitud);
Cuadrado(int longitud, int Ancho);
~Cuadrado(){}
long ObtenerPerim() { return 4 * ObtenerLongitud(); }
};

Cuadrado::Cuadrado(int longitud):
Rectangulo(longitud,longitud) {}

Cuadrado::Cuadrado(int longitud, int ancho):
Rectangulo(longitud,ancho)
{
if (ObtenerLongitud() != ObtenerAncho())
cout << “Error no es un Cuadrado… Un Rectangulo?\n”;
}

int main()
{
int opcion;
bool fSalir = false;
Figura * sp;

while (! fSalir)
{
cout << “(1) Circulo (2) Rectangulo (3) Cuadrado (0) Salir: “;
cin >> opcion;
switch(opcion)
{
case 0: fSalir = true;
break;
case 1: sp = new Circulo(5);
break;
case 2: sp = new Rectangulo(4,6);
break;
case 3: sp = new Cuadrado(5);
break;
default:
cout << “Escriba un valor entre 0 y 3!!!\n”;
continue;
break;
}
if (fSalir) break;
sp->Dibujar();
delete sp;
cout << endl;
}
return 0;
}

Si lo compilan, el programa va a funcionar correctamente con la unica diferencia de utilizar las funciones virtuales puras para evitar las creaciones de instancias de la clase base ADT. Ahora veremos como son las jerarquias de las clases ADT y como se pueden herederar y pasar entre ellas, en el siguiente ejemplo se observa esto:

# include <iostream>

using namespace std;

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; }
virtual void Dormir() const = 0;
virtual void Comer() const = 0;
virtual void Reproducir() const = 0;
virtual void Mover() const = 0;
virtual void Hablar() const = 0;
private:
int suEdad;
};

Animal::Animal(int edad):
suEdad(edad)
{
cout << “Constructor de Animal…\n”;
}

class Mamifero : public Animal
{
public:
Mamifero(int edad): Animal(edad) {
cout << “Constructor de Animal…\n”; }
virtual ~Mamifero() {
cout << “Destructor de Animal…\n”; }
virtual void Reproducir() const
{
cout << “Reproduccion de Mamifero Representada…\n”;
}
};

class Pez : public Animal
{
public:
Pez(int edad): Animal(edad) {
cout << “Contructor de Pez…\n”; }
virtual ~Pez() { cout << “Destructor de Pez…\n”; }
virtual void Dormir() const { cout << “Pez roncando… \n”; }
virtual void Comer() const { cout << “Pez comiendo…\n”; }
virtual void Reproducir() const {
cout << “Pez poniendo huevos…\n”; }
virtual void Mover() const { cout << “Pez nadando…\n”; }
virtual void Hablar() const { }
};

class Caballo : public Mamifero
{
public:
Caballo(int edad, COLOR color):
Mamifero(edad), suColor(color)
{
cout << “Constructor de Caballo…\n”;
}
virtual ~Caballo() { cout << “Destructor de Caballo…\n”; }
virtual void Hablar() const { cout << “Yihii!… \n”; }
virtual COLOR ObtenerSuColor() const { return suColor; }
virtual void Dormir() const {
cout << “Caballo roncando…\n”; }
virtual void Comer() const {
cout << “Caballo comiendo…\n”; }
virtual void Mover() const {
cout << “Caballo corriendo…\n”; }
protected:
COLOR suColor;
};

class Perro : public Mamifero
{
public:
Perro(int edad, COLOR color):
Mamifero(edad), suColor(color)
{
cout << “Constructor de Perro…\n”;
}
virtual ~Perro() { cout << “Destructor de Perro…\n”; }
virtual void Hablar() const { cout << “Guau!…\n”; }
virtual void Dormir() const {
cout << “Perro roncando…\n”; }
virtual void Comer() const {
cout << “Perro comiendo…\n”; }
virtual void Mover() const {
cout << “Perro moviendo…\n”; }
virtual void Reproducir() const {
cout << “Perro reproduciendose…\n”; }
protected:
COLOR suColor;
};

int main()
{
Animal *apAnimal=NULL;
int opcion;
bool fSalir = false;

while(1)
{
cout << “(1)Perro (2)Caballo (3)Pez (0)Salir : “;
cin >> opcion;

switch(opcion)
{
case 1: apAnimal = new Perro(5, Cafe);
break;
case 2: apAnimal = new Caballo(4, Negro);
break;
case 3: apAnimal = new Pez(5);
break;
default: fSalir = true;
break;
}

if (fSalir)
break;

apAnimal->Hablar();
apAnimal->Comer();
apAnimal->Reproducir();
apAnimal->Mover();
apAnimal->Dormir();
delete apAnimal;
cout << endl;
}
return 0;
}

En este ejemplo, vemos a la clase ADT llamada Animal, la cual va a ser base de todas las demas, en ella vamos a tener dos metodos virtuales para manipular la edad y a su vez derivarlas en el resto de las clases, en esta clase tambien vamos a tener cinco metodos virtuales puros (Hablar, Dormir, Comer, Mover y Reproducir), ahora pasemos a Mamifero la cual va a ser heredera de Animal, en esta clase solamente redefiniremos Reproducir para mostrar la reproduccion de todos los derivados de Mamifero, el resto de los metodos virtuales puros al no ser redefinidos implica que la clase Mamifero tambien va a ser ADT para las derivadas de la misma, sigue la clase Pez esta es heredera de Animal pero en cambio en esta redefinimos a los metodos virtuales derivados de Animal porque de esta clase vamos a crear objetos (o instancias) para poder trabajar con ella, luego tenemos a Caballo esta clase va a ser derivada de Mamifero y va a redefinir cuatro de los metodos virtuales puros de Mamifero (Hablar, Dormir, Comer y Mover) pero en esta clase vamos a utilizar el metodo Reproducir derivado de Mamifero, el cual fue redefinido de Animal, y por ultimo tenemos a Perro este tambien es derivado de Mamifero para este caso tambien se redefinen los metodos virtuales puros derivados de Mamifero (incluido Reproducir), despues en el main vamos a tener un ciclo donde preguntare por los tres tipos de animales a ver en pantalla (Pez, Caballo y Perro), donde previamente creamos un apuntador de la clase Animal y aparcarlo, una vez elegido el tipo de Animal, vamos a redefinir el apuntador, una vez definido procederemos a ejecutar los metodos de la clase elegida, aca les muestro la salida del programa:

(1)Perro (2)Caballo (3)Pez (0)Salir : 1
Constructor de Animal…
Constructor de Animal…
Constructor de Perro…
Guau!…
Perro comiendo…
Perro reproduciendose…
Perro moviendo…
Perro roncando…
Destructor de Perro…
Destructor de Animal…
Destructor de Animal…

(1)Perro (2)Caballo (3)Pez (0)Salir : 2
Constructor de Animal…
Constructor de Animal…
Constructor de Caballo…
Yihii!…
Caballo comiendo…
Reproduccion de Mamifero Representada…
Caballo corriendo…
Caballo roncando…
Destructor de Caballo…
Destructor de Animal…
Destructor de Animal…

(1)Perro (2)Caballo (3)Pez (0)Salir : 3
Constructor de Animal…
Contructor de Pez…
Pez comiendo…
Pez poniendo huevos…
Pez nadando…
Pez roncando…
Destructor de Pez…
Destructor de Animal…

(1)Perro (2)Caballo (3)Pez (0)Salir : 0

En la salida se puede ver como los metodos de cada clase son utilizados, en el caso de Pez al ser todos redefinidos (los peces no se reproducen igual a los mamiferos) pero tampoco “hablan” pero igualmente lo redefinimos para al momento de ser invocado no nos diera un error de compilacion, recuerden que el ADT no puede tener instancias, si nos fijamos en Perro ahi redefinimos todos los metodos virtuales puros de Animal y los podemos ver en la salida en cambio en Caballo vemos al Reproducir derivado de Mamifero y el resto de los metodos redefinidos en su clase. Entonces, como podemos ver la clase Animal solamente hace de una interfaz para derivar metodos a las clases siguientes, en el caso de no redefinirlas en las clases venideras estas se convertiran en ADT como vieron con Animal y Mamifero, estas son clases ADT pero a su vez pueden tener metodos para derivar y ser utilizadas en las clases herederas de estas, tengan en cuenta que no van a poder ser creadas instancias de estas clases.
Hemos visto herencias multiples, desde el post anterior hasta aqui,  hemos visto desde las comunes, hemos evitado su ambigüedad, las hemos tratado como virtuales para evitar la ambigüedad, y finalmente hemos tratado datos abstractos y como manejarlos entre distintas clases. Los programadores en gral. tratan de evitar las herencias multiples argumentando que no todos los compiladores lo soportan, es dificil de depurar y las mayorias de las ventajas pueden ser reemplazadas, por otro lado permite implementar en una clase metodos de distintas clases lo cual puede ser util para determinados programas, lo ideal seria una mezcla entre la herencia multiple y la herencia simple, en este caso llamado mezcla es lo mas parecido a lo visto en ADT pero como digo siempre va a quedar a criterio del programador en utilizar un metodo u otro para realizar sus programas, espero les haya sido de utilidad por lo pronto me dio una idea para un proximo programa, nos vemos en el proximo post.

Anuncios