Curso de C++ Builder
Programacin Orientada a Objetos en C++
5.1. El Paradigma de la POO en C++.
5.2. Creacin y Destruccin de Objetos.
5.3. Encapsulamiento.
5.4. Constructores y Destructores (Inicializacin de Clases
I).
5.5. Herencia.
5.5.1. Herencia de Constructores y Destructores (Inicializacin
de Clases II).
5.5.2. Clases Abstractas.
5.5.3. Herencia Mltiple.
5.6. Abstraccin.
5.6.1. Restricciones de acceso en C++.
5.6.2. Propiedades Virtuales.
5.7. Polimorfismo.
5.7.1. Sobrecarga de funciones..
5.7.2. Polimorfismo en las clases y mtodos virtuales..
En esta seccin no se pretende dar una teora completa de P.O.O.
tan slo se presentarn los conceptos necesarios para una correcta
programacin en C++ Builder.
La P.O.O. es un paradigma de programacin que se fundamenta en
los conceptos deobjetoyclase. En primer lugar, definamos que
entendemos por objeto y clase:
Objeto:
Una entidad autnoma con una funcionalidad concreta y bien
definida.
Clase:
Especificacin de las caractersticas de un conjunto de objetos.Un
objeto es unainstanciade una clase.
Los conceptos presentados en esta seccin se ilustrarn usando un
ejemplo que se ir completando poco a poco a medida que se
introduzcan nuevos conceptos. Es ms, este mismo ejemplo se emplea
en las secciones dedicadas altratamiento de excepcionesy a
laprogramacin con hebras. As, preparemos el camino creando un
proyecto:
Crear un proyecto (File | New | Application)
Cambiar el nombre del formulario (Name=PpalFrm). Colocar
unPaintBoxde la pgina de componentesSystemque se llamePaintBox,
conAlign=alTop. Dejar espacio por debajo delPaintBoxpara colocar un
botn.
Colocar unbevelde ancho 4 y alinearlo en lo alto (Align=alTop).
La idea es que delimite la parte inferior delPaintBox.
Colocar unbotn bitque permita terminar la ejecucin del programa.
El botn estar centrado horizontalmente en la parte inferior del
formulario.
Guardar el cdigo del formulario comoPpal.cppy el proyecto
comoEjemplo.bpr.
Crear una unidad (File | New | Unit). Guardarla con el
nombreObjGraf.cpp
Cuando se crea una unidad de esta manera se crean, en realidad,
dos ficheros, uno con extensin.cppy otro con extensin.h. As,
disponemos de dos ficheros:ObjGraf.h, que contendr las
declaraciones de las clases con las que vamos a trabajar,
yObjGraf.cpp, que contendr la definicin (implementacin de los
mtodos) de stas.
EnObjGraf.h:
//--------------------------------------------------
#ifndef ObjGrafH
#define ObjGrafH
// Definicin de la clase TObjGraf
class TObjGraf {};
#endif
//--------------------------------------------------
Ntese que el nombre de la clase va precedido por unaT, y, aunque
no es obligatorio, si es muy recomendable ya que es una convencin
deC++ Builderque casi todos los nombres de clases vayan precedidos
porT.
Muy Importante:Con el ejemplo anterior slo conseguimos definir
la clase, pero no se crea ningn objeto.
5.1. El Paradigma de la POO en C++
Existen cuatro principios bsicos que cualquier sistema orientado
a objetos debe incorporar, que se esquematizan en la figura
5.1.
Figura 5.1. Pilares de la POO.
5.2. Creacin y Destruccin de Objetos
Ya se ha dicho que una clase es nicamente unaespecificacin. Para
poder utilizar la funcionalidad contenida en la misma, se
debeninstanciarlas clases.
1. Creacin por Declaracin.
Un objeto se puede instanciar de una forma simple, declarando
una variable del tipo de la clase.
EnPpal.h:
#include "ObjGraf.h"
EnPpal.cpp:
Pulsando dos veces enOnCreatede la pestaaEventsdel editor de
objetos dePpalFrm:
//--------------------------------------------------
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
{
TObjGraf ObjGraf1();
TObjGraf ObjGraf2;
}
//--------------------------------------------------
Aunque esta forma es posible, y bastante utilizada en la
programacin de C++ clsica, enC++ Builderse utiliza en muy contadas
ocasiones. Esto es as por dos razones, fundamentalmente:
1. La duracin de los objetos suele ir ms all de una simple
funcin o bloque. Debido al enfoque de la programacin dirigida por
eventos, suele ser habitual que un objeto se cree en un gestor de
eventos y se destruya en otro.
2. No se puede usar esta modalidad de creacin con la VCL.
Por lo tanto, nosotrosnola utilizaremos.
2. Creacin Dinmica
Es la forma habitual de crear objetos enC++ Builder, y se
realiza mediante el operadornew.
Cuando usamosnewpara instanciar un objeto, se usa una variable
quereferencieoapunteal nuevo objeto creado (de otra manera ste
quedara totalmente inaccesible). En definitiva, se requiere la
declaracin previa de unpunteroa objetos del tipo del que se va a
crear.
EnPpal.cpp:
TObjGraf * ObjGraf; // Variable Global.
// ObjGraf es un puntero a objetos de tipo TObjGraf
//--------------------------------------------------
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
{
ObjGraf = new TObjGraf;
}
//--------------------------------------------------
La forma de establecer el estado inicial o destruir las
componentes de un objeto se estudiarn en el apartado dedicado
aConstructores y Destructores(seccin 5.4).
Cuidado!Cuando se utiliza esta forma de instanciacin de clases
es responsabilidad nicamente del programador la correcta destruccin
de los mismos.
3. Destruccin de objetos
Cuando un objeto deja de ser til hay que eliminarlo. De esta
manera la aplicacin recupera los recursos (memoria) que ese objeto
haba acaparado cuando se cre.
La destruccin de objetos creados en tiempo de ejecucin connewse
realiza mediante el operadordelete.
EnPpal.cpp:
Pulsando dos veces enOnDestroyde la pestaaEventsdel editor de
objetos dePpalFrm:
//--------------------------------------------------
void __fastcall TPpalFrm::FormDestroy(TObject *Sender)
{
delete ObjGraf;
}
//--------------------------------------------------
5.3. Encapsulamiento
En la programacin clsica (lenguajeC, p.e.) existen datos y
procedimientos que actan sobre esos datos. No hay una relacin
aparente entre datos y procedimientos (funciones) y esta relacin se
establece de manera ms o menos pecisa de acuerdo a la
profesionalidad del programador.
En un objeto podemos distinguir dos aspectos bien
diferenciados:
Estado----------->Propiedades
Comportamiento--->Mtodos
En P.O.O. los datos y los procedimientos que los gestionan estn
relacionados explcitamente y se "encapsulan" en un objeto. La
especificacin de las propiedades de un objeto y los mtodos de
acceso se realiza en la declaracin de la clase de la que se
instancia el objeto.
En la figura 5.2 esquematizamos las propiedades y mtodos que se
van a asociar a los objetos de la claseTObjGraf:
Figura 5.2. Propiedades y mtodos de los objetos de la
claseTObjGraf.
La declaracin de propiedades y mtodos de los objetos de la
claseTObjGrafse realiza de la siguiente manera:
EnObjGraf.h:
//--------------------------------------------------
class TObjGraf {
public:
int X; // Propiedades
int Y;
TColor Color;
TPaintBox *PaintBox;
void Mostrar (void); // Mtodos
};
//--------------------------------------------------
Acceso a Miembros de un Objeto
Para acceder a los miembros de un objeto se usan los operadores
tpicos de acceso a miembros: el operador.para referencia directa al
objeto y el operador->para acceso a travs de un puntero. Como
nosotros siempre creamos los objetos connew, y los referenciamos
mediante un puntero, el operador de acceso que utilizaremos es el
operador->
EnPpal.cpp:
//--------------------------------------------------
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
{
...
int ValorY;
...
ObjGraf->X = 5;
ValorY = ObjGraf->Y;
ObjGraf->Mostrar(); //Equivalente a (*Obj).Mostrar();
}
//--------------------------------------------------
Nota:Los puntos suspensivos no son una palabra reservada de C++,
simplemente significan que se omite una parte del cdigo, ya sea
porque es irrelevante o porque ya se ha expuesto anteriormente.
5.4. Constructores y Destructores (Inicializacin de Clases
I)
Son mtodos que permiten establecer el estado inicial y final de
un objeto. Los constructores se pueden definir con un conjunto de
argumentos arbitrario, pero no pueden devolver nada. Y los
destructores no pueden recibir ni devolver ningn valor.
El constructor debe llamarse igual que la clase, y el destructor
el nombre de la clase precedido del carcter~
Un constructor se ejecuta cuando se crea un nuevo objeto: 1) por
declaracin, 2) cuando se crea dinmicamente con el operadornew. Un
destructor se ejecuta cuando el objeto deja de existir: 1) porque
su mbito acaba, 2) cuando se libera explcitamente con el
operadordelete.
EnObjGraf.h:
class TObjGraf {
...
// Constructor de objetos TObjGraf
TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0);
// El destructor sera: ~TObjGraf (void);
};
EnObjGraf.cpp:
TObjGraf :: TObjGraf (TPaintBox * _PaintBox, TColor _Color,
int _X, int _Y)
{
PaintBox = _PaintBox;
Color = _Color;
X = _X;
Y = _Y;
}
EnPpal.cpp:
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
{
ObjGraf = new TObjGraf (PaintBox, clRed, 10, 10);
}
Importante:No es necesario escribir un destructor salvo si el
objeto requiere memoria dinmica adicional. De ser as, la tarea del
destructor ser, bsicamente, liberar la memoria dinmica que ocupa el
objeto que se va a destruir.
5.5. Herencia
Cuando una clase hereda de otra, laclase derivadaincorpora todos
los miembros de laclase baseadems de los suyos propios.
La herencia es una herramienta muy importante en muchos aspectos
del desarrollo de aplicaciones:
Organizacin del diseo.
Reusabilidad de clases (propias o no).
Mejora del mantenimiento.
Tomando como base la claseTObjGrafse van a construir dos nuevas
clases,TCirculoyTCuadrado, quederivandeTObjGraf. Esto significa que
los objetos de estas clases tienen asociados las propiedades y
mtodos de la clase base,TObjGraf, adems de los suyos propios. En la
figura 5.3 esquematizamos el mecanismo de herencia para las nuevas
clases y las nuevas propiedades que se asocian a los objetos de las
clases derivadas.
Figura 5.3. Las clasesTCirculoyTCuadradoheredan las propiedades
y mtodos de la claseTObjGraf.4
EnObjGraf.h:
//*************************************************/
// Definicion de la clase derivada TCirculo
// Deriva de la clase base TObjGraf
//*************************************************/
class TCirculo : public TObjGraf {
public:
int Radio; // Propiedad exclusiva de TCirculo
};
//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/
class TCuadrado : public TObjGraf {
public:
int Lado; // Propiedad exclusiva de TCuadrado
};
Antes del nombre de la clase base hay que ponerpublic, esto es
as porque C++ permite tambin la herenciaprivate. Pero sta no se
suele usar, por lo que nosotros supondremos que slo existe
lapublic.
5.5.1. Herencia de Constructores y Destructores (Inicializacin
de Clases II)
Los constructores y destructores de una clasenoson heredadas
automticamente por sus descendientes. Deberemos crear en las clases
hijas sus propios constructores y destructores. Es posible, no
obstante, emplear los constructores de la clase base pero hay que
indicarloexplcitamente. De ser as, es necesario saber:
1. que los constructores y destructores de las clases base son
invocados automticamenteantesque los constructores de las clases
derivadas, y
2. que los destructores de las clases derivadas se
invocanantesque los de las clases base.
Para determinar con qu parmetros se llaman a los constructores
de las clases base, se utiliza lalista de inicializacin.
EnObjGraf.h:
//*************************************************/
// Definicion de la clase derivada TCirculo
// Deriva de la clase base TObjGraf
//*************************************************/
class TCirculo : public TObjGraf {
public:
int Radio; // Propiedad exclusiva de TCirculo
// Metodo constructor
TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0, int _Radio=1);
};
//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/
class TCuadrado : public TObjGraf {
public:
int Lado; // Propiedad exclusiva de TCuadrado
// Metodo constructor
TCuadrado (TPaintBox * _PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0, int _Lado=1);
};
EnObjGraf.cpp:
TCirculo :: TCirculo (TPaintBox * _PaintBox, TColor _Color,
int _X, int _Y, int _Radio) :
TObjGraf (_PaintBox, _Color, _X, _Y)
{
Radio = _Radio;
}
TCuadrado :: TCuadrado (TPaintBox * _PaintBox, TColor
_Color,
int _X, int _Y, int _Lado) :
TObjGraf (_PaintBox, _Color, _X, _Y)
{
Lado = _Lado;
}
5.5.2. Clases Abstractas
Clase abstracta: es una clase que no est completamente
especificada (posee mtodos sin implementar), por lo tantono se
pueden crear instancias de la misma. Una clase abstracta se usa
para servir de clase base a otras clases. En terminologa C++ se
dice que una clase abstracta es aquella que posee al menos unmtodo
virtual puro.
Virtual:obliga a las clases derivadas a implementar ese
mtodo.
Puro:no pueden crearse instancias de esa clase.
EnObjGraf.h:
class TObjGraf {
public:
...
// Otros metodos
virtual void Mostrar (void) = 0; // Metodo virtual puro
};
class TCirculo : public TObjGraf {
public:
...
// Instanciacion del metodo virtual puro de la clase
TObjGraf
void Mostrar (void);
};
class TCuadrado : public TObjGraf {
public:
...
// Instanciacion del metodo virtual puro de la clase
TObjGraf
void Mostrar (void);
};
EnObjGraf.cpp:
void TCirculo :: Mostrar (void)
{
PaintBox->Canvas->Pen->Color = Color;
PaintBox->Canvas->Brush->Color = Color;
PaintBox->Canvas->Ellipse(X, Y, X+Radio*2, Y+Radio*2);
}
...
void TCuadrado :: Mostrar (void)
{
PaintBox->Canvas->Pen->Color = Color;
PaintBox->Canvas->Brush->Color = Color;
PaintBox->Canvas->Rectangle(X, Y, X+Lado, Y+Lado);
}
Por qu se especifica el mtodoMostrarenTObjGraf, como virtual
puro, en lugar de omitirlo? Fundamentalmente podemos considerar dos
razones para usar mtodos virtuales puros:
1. Para obligar a que las clases descendientes los implementen.
De esta forma estamos seguros de que todas las clases descendientes
no abstractas deTObjGrafposeen el mtodo, y se podr invocar con
seguridad.
2. Para evitar que se puedan crear instancias de la clase
abstracta.
En este estado, si probamos a ejecutar el programa, nos aparecer
un error: no se puede crear una instancia de una clase abstracta.
Porqu?: Recordar que enPpal.cppel gestor asociado al
eventoOnCreatedel formulario est escrito como sigue:
//--------------------------------------------------
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
{
ObjGraf = new TObjGraf (PaintBox, clRed, 10, 10);
};
//--------------------------------------------------
As, creemos entonces objetos de las clases hijas:
1. En primer lugar, enPpal.cpphay que borrar la declaracin de la
variable global:
2. TObjGraf *ObjGraf; // Variable Global.
y en su lugar se declararn cuatro punteros, dos para referenciar
a objetos de tipoTCirculoy otros dos para referenciar a objetos de
tipoTCuadrado:
// Punteros a objetos de las clases derivadas.
TCirculo *Cir1, *Cir2;
TCuadrado *Cuad1, *Cuad2;
3. En segundo lugar, modificaremos la funcinFormCreatepara que
cree dos objetos de cada clase referenciados por los punteros
declarados anteriormente:
4. //--------------------------------------------------
5.
6. void __fastcall TPpalFrm::FormCreate(TObject *Sender)
7. {
8. Cir1 = new TCirculo (PaintBox, clBlack, 100, 100, 30);
9. Cir2 = new TCirculo (PaintBox, clGreen, 210, 40, 20);
10. Cuad1 = new TCuadrado (PaintBox, clRed, 200, 150, 45);
11. Cuad2 = new TCuadrado (PaintBox, clYellow, 120, 70, 25);
12. };
13.
14. //--------------------------------------------------
15. Finalmente, modificaremos la funcinFormDestroypara que
elimine los objetos creados:
16. //--------------------------------------------------
17.
18. void __fastcall TPpalFrm::FormDestroy(TObject *Sender)
19. {
20. delete Cir1;
21. delete Cir2;
22. delete Cuad1;
23. delete Cuad2;
24. }
25.
26. //--------------------------------------------------
Ahora, al ejecutar el programa se crean y se destruyen objetos
de las clases derivadas, aunque no se visualizan en la ventana.
Porqu? En ningn momento se ha llamado al mtodoMostrar()asociado a
cada objeto. Para mostrar los objetos, basta con indicarlo en el
gestor asociado al eventoOnPaintdel componentePaintBox:
//--------------------------------------------------
void __fastcall TPpalFrm::PaintBoxPaint(TObject *Sender)
{
Cir1->Mostrar();
Cir2->Mostrar();
Cuad1->Mostrar();
Cuad2->Mostrar();
}
//--------------------------------------------------
En este punto, el proyecto debe estar como se indica en el
proyectoEjemplo0.
El resultado es el mostrado en la figura 5.4:
4Figura 5.4. Resultado del proyectoEjemplo.bpr.
Ejercicio:
Construir la claseTTrianguloy modificar el proyecto para que
proporcione un resultado similar al de la figura 5.5.
Figura 5.5. Resultado del proyectoEjemplo.bprmostrando objetos
de la claseTTriangulo.
5.5.3. Herencia Mltiple
La herencia mltiple es el hecho de que una clase derivada se
genere a partir de varias clases base.
Ejemplo:
En un concesionario de coches podramos considerar la siguiente
jerarqua de clases:
class TProducto {
long Precio;
...
};
class TVehiculo {
int NumRuedas;
...
};
class TCocheEnVenta : public TProducto, public TVehiculo {
...
};
Observar que los objetos de la claseTCocheEnVentaderivan de las
clasesTProductoyTVehiculo.
Existen dos formas para que una clase saque partido de las
ventajas de otra, una es la herencia, y la otra es que una clase
contenga un objeto de la otra clase.
Ninguna de las dos posibilidades es mejor que la otra, en cada
caso particular habr que estudiar cual es la mejor opcin.
Por ejemplo, si quisieramos disear una clase (TMarco) que
represente un marco (representado por un cuadrado y un crculo),
podemos decidir distintas estrategias a la hora de llevarlo a
cabo:
Que herede deTCirculoyTCuadrado.
Que herede deTObjGrafy contenga un objeto de la claseTCirculoy
otro deTCuadrado.
Que herede deTCirculoy contenga un objeto de la
claseTCuadrado.
Que herede deTCuadradoy contenga un objeto de la
claseTCirculo.
5.6. Abstraccin
Es la ocultacin de detalles irrelevantes o que no se desean
mostrar. Podemos distinguir en una clase dos aspectos desde el
punto de vista de la abstraccin:
Interfaz: lo que se puede ver/usar externamente de un
objeto.
Implementacin: cmo lleva a cabo su cometido.
Resumiendo:nos interesa saber qu nos ofrece un objeto, pero no
cmo lo lleva a cabo.
5.6.1. Restricciones de acceso en C++
En C++ se puede especificar el acceso a los miembros de una
clase utilizando los siguientes especificadores de acceso:
public: Interfaz de la clase.
private: Implementacin de la clase.
protected: Implementacin de la familia.
Estos especificadores no modifican ni la forma de acceso ni el
comportamiento, nicamente controlan desde dnde se pueden usar los
miembros de la clase:
public: desde cualquier sitio.
private: desde los mtodos de la clase.
protected: desde los mtodos de la clase y desde los mtodos de
las clases derivadas.
EnObjGraf.h:
//*************************************************/
// Definicion de la clase base TObjGraf
//*************************************************/
class TObjGraf {
private: // Puede acceder SOLO los objetos de esta clase.
int X;
int Y;
protected: // Pueden acceder los objetos de esta clase y sus
descendientes.
TColor Color;
TPaintBox * PaintBox;
public: // Pueden usarlas todas.
// Constructor de objetos TObjGraf
TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0);
// Otros metodos
virtual void Mostrar (void) = 0; // Metodo virtual puro
};
Modificar de la misma manera las clasesTCirculoyTCuadradopara
que sus propiedadesRadioyLadoqueden protegidas y los mtodos
pblicos:
//*************************************************/
// Definicion de la clase derivada TCirculo.
// Deriva de la clase base TObjGraf
//*************************************************/
class TCirculo : public TObjGraf {
protected: // Pueden acceder los objetos de esta clase y sus
descendientes.
int Radio;
public:
// Metodo constructor
TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0, int _Radio=1);
void Mostrar (void); // Instanciacion del metodo virtual
puro
// de la clase TObjGraf
};
//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/
class TCuadrado : public TObjGraf {
protected: // Pueden acceder los objetos de esta clase y sus
descendientes.
int Lado;
public:
// Metodo constructor
TCuadrado (TPaintBox * _PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0, int _Lado=1);
void Mostrar (void); // Instanciacion del metodo virtual
puro
// de la clase TObjGraf
};
As, si enPpal.cppescribiramos:
//--------------------------------------------------
void __fastcall TPpalFrm::FormCreate (TObject *Sender)
{
...
Cir1->Mostrar(); // Se puede.
Cir1->X = 10; // No se puede porque X es private.
}
//--------------------------------------------------
En realidad estos tres especificadores de acceso son los propios
deC++, pero enC++ Builderexiste otro adicional, que es
el__published. No vamos a dar mucha importancia a este modificador,
porque su uso est restringido alIDE. Cuando en una clase veamos una
seccin__publishedquiere decir que los miembros contenidos en la
misma son mantenidos automticamente por el IDE y no deberemos
modificar nada en dicha seccin, ya que de lo contrario los
resultados pueden ser imprevisibles.
Es una buena tcnica de programacin no permitir el acceso pblico
a las propiedades de un objeto, ya que si esto ocurriera podra
peligrar la integridad del objeto. Entonces cmo se puede cambiar el
estado de un objeto desde el exterior?
1. Ofreciendo mtodos (pblicos) que se encarguen de modificar las
propiedades (privadas) que se desee. De esta manera son los mtodos
los que acceden a las propiedades y el usuario de la clase slo
accede a travs de ellos. Esta es la tcnica clsica que se emplea en
C++
2. A travs de los mtodos y de las propiedades "virtuales". Esta
tcnica es exclusiva de C++ Builder y la describimos en la siguiente
seccin.
5.6.2. Propiedades Virtuales
Son propiedades definidas mediante mtodos de lectura (read) y/o
escritura (write). Se llaman virtuales porque, realmente, no
existen. El usuario de la clase usa estas propiedades como si
fueran propiedades reales y en ltima instancia se traducen en la
llamada a un mtodo o en el acceso a una propiedad real. Es ms, si
una propiedad virtual se usa para lectura (p.e. en la parte derecha
de una asignacin) se traduce en una accin diferente que si esa
popiedad virtual se usa para escritura. La accin que se produce
cuando la propiedad virtual es de lectura se especifica,
sintcticamente, mediante la palabra reservadareadmientras que si se
usa para escritura se especifica conwrite. Veamos un ejemplo.
EnObjGraf.h:
//*************************************************/
// Definicion de la clase base TObjGraf
//*************************************************/
class TObjGraf {
private: // Puede acceder SOLO los objetos de esta clase.
int FX; // OJO: Se ha cambiado el nombre a
int FY; // los miembros X e Y por FX y FY.
void SetX (int _X);
void SetY (int _Y);
virtual int GetAncho (void) = 0; // Metodo virtual puro
virtual int GetAlto (void) = 0; // Metodo virtual puro
protected: // Pueden acceder los objetos de esta clase y
descendientes.
TColor FColor; // OJO: Se ha cambiado Color por FColor
TPaintBox *PaintBox;
public: // Pueden usarlas todas.
// Constructor de objetos TObjGraf
TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0);
// Otros metodos
virtual void Mostrar (void) = 0; // Metodo virtual puro
// NUEVAS FORMAS DE ACCESO CON PROPIEDADES VIRTUALES.
__property int X = {read=FX, write=SetX };
__property int Y = {read=FY, write=SetY };
__property TColor Color = {read=FColor, write=FColor};
__property int Ancho = {read=GetAncho };
__property int Alto = {read=GetAlto };
};
Observaciones:
Observar que la "antigua" propiedadX(resp.Y) se llama
ahoraFX(resp.FY). Adems, hay una propiedad virtual (pblica)
llamadaX(resp.Y). Estas propiedades estn declaradas en la
claseTObjGraflo que significa que sus descendientes tambin las
heredan.
Si enPpal.cppse usara esta propiedad para lectura:
int CX = Cir1->X;
En realidad es como si se hiciera
int CX = Cir1->FX;
ya que cuando se accede para lectura a la propiedad (virtual)Xen
realidad se accede a la propiedad (real)FX. La ltima instruccin, no
obstante, povocara un error porqueFXes una propiedad privada.
Si se usara esta propiedad para escritura:
Cir1->X = 100;
En realidad es como si se hiciera
Cir1->SetX(100);
ya que cuando se accede para escritura a la propiedad
(virtual)Xen realidad se llama al mtodoSetX(). La ltima instruccin,
no obstante, povocara un error porqueSetX()es un mtodo privado. Al
"redirigir" la escritura al mtodoSetX()podemos controlar la validez
del parmetro y corregir, en su caso, el valor: una ventaja
adicional.
La propiedad virtualColortiene asociado el mismo mtodo de acceso
para lectura y escritura: devuelve o escribe, directamente, en la
propiedad realFColor.
Finalmente, observar que las propiedades virtualesAnchoyAltono
tienen asociados mtodos de acceso para escritura.
Como hemos incorporado dos nuevos mtodos a la clase
baseTObjGraf(GetAncho()yGetAlto()) y stos se han declarado
virtuales puros necesitamos instanciarlos en las clases
derivadas:
//*************************************************/
// Definicion de la clase derivada TCirculo.
// Deriva de la clase base TObjGraf
//*************************************************/
class TCirculo : public TObjGraf {
protected: // Pueden acceder los objetos de esta clase y
descendientes.
int Radio;
inline int GetAncho (void) {return(Radio*2);}
inline int GetAlto (void) {return(Radio*2);}
public:
// Metodo constructor
TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0, int _Radio=1);
void Mostrar (void); // Instanciacion del metodo virtual puro
de
// la clase TObjGraf
};
//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/
class TCuadrado : public TObjGraf {
protected: // Pueden acceder los objetos de esta clase y
descendientes.
int Lado;
inline int GetAncho (void) {return(Lado);}
inline int GetAlto (void) {return(Lado);}
public:
// Metodo constructor
TCuadrado (TPaintBox * _PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0, int _Lado=1);
void Mostrar (void); // Instanciacion del metodo virtual puro
de
// la clase TObjGraf
};
Ahora, aadimos (enObjGraf.cpp) las funcioneswritede las
propiedades virtualesXeY:
// Funciones de escritura de las propiedades virtuales X e Y
void TObjGraf :: SetX (int _X)
{
if (_X < 0) // Coordenada negativa
FX = 0; // Ajustar al margen izquierdo
else
if (_X > (PaintBox->Width - Ancho)) // Demasiado alta
FX = PaintBox->Width - Ancho; // Ajustar al margen
derecho
else
FX = _X; // Correcto: escribir sin modificar
}
void TObjGraf :: SetY (int _Y)
{
if (_Y < 0) // Coordenada negativa
FY = 0; // Ajustar al margen superior
else
if (_Y > (PaintBox->Height - Alto)) // Demasiado alta
FY = PaintBox->Height - Alto; // Ajustar al margen
inferior
else
FY = _Y; // Correcto: escribir sin modificar
}
Muy importante:Cambiamos el constructor de la
claseTObjGrafporque no se puede llamar a los mtodos virtuales puros
de una propiedad virtual desde un constructor de una clase
base.
En este caso, no se puede llamar a los mtodos virtuales puros
(GetAncho()yGetAlto()) de las propiedades virtuales (AnchoyAlto)
desde el constructor de la clase baseTObjGraf.
EnObjGraf.cpp:
TObjGraf :: TObjGraf (TPaintBox *_PaintBox,
TColor _Color, int _X, int _Y)
{
PaintBox = _PaintBox;
FColor = _Color; // MUY IMPORTANTE: Estas tres lineas han
cambiado:
FX = _X; // No se puede llamar a los metodos virtuales puros
FY = _Y; // (GetAncho, GetAlto) para fijar el valor de una
// propiedad virtual (Alto, Ancho) desde un
// constructor de la clase base.
}
En este punto, el proyecto debe estar como se indica en el
proyectoEjemplo1.
El resultado el el mismo que el mostrado en la figura 5.4.
Experimentacin con las propiedades virtuales.
1. Los constructores no verifican coordenadas.
Los objetos grficos (Cir1,Cir2,Cuad1yCuad2) se crean con sus
correspondientes constructores, que en ltima instancia llaman al
constructor de la clase base para establecer los valores de las
propiedades realesFX,FY,FColoryPaintBox. La consecuencia es que si
se crea un crculo, por ejemplo, con coordenadas incorrectasnose
corrigen ya que como el constructor de la clase base no puede
utilizar la propiedad virtualXpara escritura no se emplea el
mtodoSetX().
Como demostracin, en la funcinFormCreate()dePpal.cppcambiar la
lnea:
Cir1 = new TCirculo (PaintBox, clBlack, 100, 100, 30);
por:
Cir1 = new TCirculo (PaintBox, clBlack, -100, 100, 30);
Observar que el crculo negro no se muestra.
2. Lectura/escritura a travs de propiedades virtuales.
Modificar la funcinPaintBoxPaint()dePpal.cppaadiendo esta lnea
al principio:
if (Cir1->X < 0) Cir1->X = 100;
Observar que la propiedad virtualXse usa para lectura y
escritura. La lnea anterior fija la coordenadaXal valor 100.
Un ejemplo ms clarificador es cambiar la lnea anterior por:
if (Cir1->X < 0) Cir1->X = -200;
Observar que el crculo negro tiene el valor0en la coordenadaX:
este valor se ha fijado a travs del mtodo de escrituraSetX().
5.7. Polimorfismo
Es demostrar comportamientos distintos segn la situacin. Puede
darse de tres formas diferentes:
Funciones: sobrecarga.
Clases: es al que se refiere normalmente el concepto de
polimorfismo.
Enlace dinmico: mtodos virtuales.
5.7.1.Sobrecarga de funciones.
Ocurre cuando en una clase existen dos mtodos con idntico nombre
pero con distinta lista de parmetros. El compilador los considera
como dos mtodos distintos y aplicar cada uno de ellos en la
situacin apropiada.
Por ejemplo, podra sobrecargarse el constructor de la
claseTObjGrafaadiendo un nuevoconstructor de copia:
EnObjGraf.h:
TObjGraf (TObjGraf *ObjGraf);
EnObjGraf.cpp:
TObjGraf::TObjGraf (TObjGraf *ObjGraf)
{
PaintBox = ObjGraf->PaintBox;
FColor = ObjGraf->Color;
FX = ObjGraf->FX;
FY = ObjGraf->FY;
}
5.7.2. Polimorfismo en las clases y mtodos virtuales.
Una clase se puede comportar como cualquiera de sus antecesoras
(en la asignacin por ejemplo). Como tenemos variables (punteros)
que pueden contener objetos de distintas clases, el compilador no
sabe qu tipo de objeto es al que realmente apunta la variable (en
tiempo de compilacin) por lo tanto hay retrasar el enlace a tiempo
de ejecucin.
Elenlace dinmicoes retrasar el enlace de una llamada a un mtodo
(funcin) al tiempo de ejecucin.
Para ilustrar el polimorfismo, crearemos una nueva
clase,TPelota, que derive de la claseTCirculo. Una pelota (un
objeto de tipoTPelota, para ser ms precisos) es un crculo que tiene
la capacidad de movimiento. Para implementar el movimiento de una
pelota necesitamos incorporar nuevas propiedades y mtodos propios a
la claseTPelota. Sin embargo, en este momento nos interesa poner de
manifiesto el polimorfismo, lo que conseguimos a travs del
mtodoMostrar()asociado a la claseTPelota. Antes, modificamos la
declaracin del mtodoMostrar()de la claseTCirculopara obligar a sus
descendientes a implementar su propio mtodoMostrar(): basta con
indicar que en el mtodoMostrar()de la claseTCirculoes virtual.
EnObjGraf.h:
//*************************************************/
// Definicion de la clase derivada TCirculo.
// Deriva de la clase base TObjGraf
//*************************************************/
class TCirculo : public TObjGraf {
private:
...
public:
...
// Ahora, el metodo Mostrar() se declara virtual, aunque no es
puro:
// 1) Por ser virtual: cualquier clase que derive de TCirculo
debe
// tener su propio metodo Mostrar(),
// 2) Por no ser puro: puede llamarse a este metodo con objetos
TCirculo.
virtual void Mostrar (void);
...
};
Ahora nos centramos en la nueva claseTPelota. Antes, por
comodidad y claridad, definimos un tipo por enumeracin para la
direccin del movimiento:
EnObjGraf.h:
// Tipo definido por enumeracion para la direccion de TPelota.
Codificacion:
/*
NO N NE
10 2 6
\ | /
O 8 --- * --- 4 E
/ | \
9 1 5
SO S SE
*/
enum TDireccion {S=1, N=2, E=4, O=8, SE=5, NE=6, SO=9,
NO=10};
La declaracin de la claseTPelotase har enObjGraf.h:
//*************************************************/
// Definicion de la clase derivada TPelota.
// TPelota deriva de la clase TCirculo, que a su
// vez deriva de la clase base TObjGraf
//*************************************************/
class TPelota: public TCirculo {
private:
int FDirX; // Dir. en el eje X
int FDirY; // Dir. en el eje Y
int FVelocidad; // Velocidad del movimiento
void SetDireccion (TDireccion _Direccion);
TDireccion GetDireccion (void);
public:
// Constructores
TPelota (TPaintBox *_PaintBox, TColor _Color=clBlack,
int _X=0, int _Y=0, int _Radio=1,
TDireccion _Direccion=SE, int _Velocidad=5);
// Otros metodos
void Mostrar (void);
void Borrar (void);
void Mover (void);
__property int Velocidad = {read = FVelocidad,
write= FVelocidad};
__property TDireccion Direccion = {read = GetDireccion,
write= SetDireccion};
};
La implementacin de los mtodos propios de la claseTPelotase har
enObjGraf.cpp:
/*****************************************************/
// Metodos asociados a la clase derivada TPelota.
// TPelota deriva de la clase TCirculo, que a su
// vez deriva de la clase base TObjGraf
/*****************************************************/
TPelota :: TPelota (TPaintBox *_PaintBox, TColor _Color, int
_X,
int _Y, int _Radio, TDireccion _Direccion, int _Velocidad) :
TCirculo (_PaintBox, _Color, _X, _Y, _Radio)
{
Direccion = _Direccion;
Velocidad = _Velocidad;
}
// Instanciacion del metodo virtual puro de la clase
TObjGraf
// y virtual en TCirculo.
void TPelota :: Mostrar (void)
{
PaintBox->Canvas->Pen->Color = clBlack; // Observar la
diferencia
PaintBox->Canvas->Brush->Color = Color;
PaintBox->Canvas->Ellipse(X, Y, X+Radio*2, Y+Radio*2);
}
// Otras funciones propias de TPelota
void TPelota :: Borrar (void)
{
PaintBox->Canvas->Pen->Color = PaintBox->Color;
PaintBox->Canvas->Brush->Color =
PaintBox->Color;
PaintBox->Canvas->Ellipse(X, Y, X+Radio*2, Y+Radio*2);
}
void TPelota :: Mover (void)
{
Borrar ();
X += FDirX * Velocidad;
Y += FDirY * Velocidad;
Mostrar ();
}
void TPelota :: SetDireccion (TDireccion _Dir)
{
FDirY = (_Dir & 1) ? +1 : ((_Dir & 2) ? -1 : 0);
FDirX = (_Dir & 4) ? +1 : ((_Dir & 8) ? -1 : 0);
}
TDireccion TPelota :: GetDireccion (void)
{
TDireccion _Dir;
_Dir = (TDireccion) ((FDirY == +1) ? 1 : ((FDirY == -1 ) ? 2 :
0));
_Dir = (TDireccion) (_Dir + (FDirX == +1) ? 4 : ((FDirX == -1 )
? 8 :0));
return (_Dir);
}
Finalmente, para ilustrar el polimorfismo nos basaremos en la
existencia de diferentes mtodos con el mismo nombre:Mostrar(), que
provocan diferentes acciones dependiendo del tipo de objetos al que
se aplican.
Vamos a crear dinmicamente cuatro objetos de diferentes clases.
Estos objetos van a ser referenciados (mediante punteros) desde un
vector de objetos de tipoTObjGraf *. El polimorfismo se va a
manifestar invocando a la funcinMostrar()para cada uno de esos
objetos.
EnPpal.cppdeclararemos la variable global:
TObjGraf **Objs;
que se interpreta como:Objses un puntero a una zona de memoria
que contendr punteros a objetos de tipoTObjGraf.
As, enPpal.cpp:
//----------------------------------------------------------------------
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
{
Objs = new TObjGraf * [4];
Objs[0] = new TCirculo (PaintBox, clBlack, 100, 100, 30);
Objs[1] = new TCuadrado (PaintBox, clRed, 200, 150, 45);
Objs[2] = new TCirculo (PaintBox, clGreen, 210, 40, 20);
Objs[3] = new TPelota (PaintBox, clYellow, 120, 70, 25);
}
//----------------------------------------------------------------------
void __fastcall TPpalFrm::FormDestroy(TObject *Sender)
{
for (int i=0; i