You are on page 1of 19

Curso de C++ Builder ________________________________________ Programacin Orientada a Objetos en C++ ________________________________________ 5.1. El Paradigma de la POO en C++. 5.2.

2. Creacin y Destruccin de Objetos. 5.3. Encapsulamiento. 5.4. Constructores y Destructores (Inicializacin de Clases I). 5.5. Herencia. o 5.5.1. Herencia de Constructores y Destructores (Inicializacin de Clases II). o 5.5.2. Clases Abstractas. o 5.5.3. Herencia Mltiple. 5.6. Abstraccin. o 5.6.1. Restricciones de acceso en C++. o 5.6.2. Propiedades Virtuales. 5.7. Polimorfismo. o 5.7.1. Sobrecarga de funciones.. o 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 de ob jeto y clase. 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 una instancia de una clase. Los conceptos presentados en esta seccin se ilustrarn usando un ejemplo que se ir c ompletando poco a poco a medida que se introduzcan nuevos conceptos. Es ms, este mismo ejemplo se emplea en las secciones dedicadas al tratamiento de excepciones y a la programacin con hebras. As, preparemos el camino creando un proyecto: Crear un proyecto (File | New | Application) Cambiar el nombre del formulario (Name=PpalFrm). Colocar un PaintBox de la pgina de componentes System que se llame PaintBox, con Align=alTop. Dejar espacio por debajo del PaintBox para colocar un botn. Colocar un bevel de ancho 4 y alinearlo en lo alto (Align=alTop). La idea es que delimite la parte inferior del PaintBox. Colocar un botn bit que permita terminar la ejecucin del programa. El botn estar trado horizontalmente en la parte inferior del formulario. Guardar el cdigo del formulario como Ppal.cpp y el proyecto como Ejemplo.bpr. Crear una unidad (File | New | Unit). Guardarla con el nombre ObjGraf.cpp Cuando se crea una unidad de esta manera se crean, en realidad, dos ficheros, un o con extensin .cpp y otro con extensin .h. As, disponemos de dos ficheros: ObjGraf .h, que contendr las declaraciones de las clases con las que vamos a trabajar, y ObjGraf.cpp, que contendr la definicin (implementacin de los mtodos) de stas. En ObjGraf.h: //-------------------------------------------------#ifndef ObjGrafH #define ObjGrafH // Definicin de la clase TObjGraf class TObjGraf {}; #endif //--------------------------------------------------

Ntese que el nombre de la clase va precedido por una T, y, aunque no es obligator io, si es muy recomendable ya que es una convencin de C++ Builder que casi todos los nombres de clases vayan precedidos por T. 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 i ncorporar, 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 una especificacin. Para poder utilizar l a funcionalidad contenida en la misma, se deben instanciar las clases. 1. Creacin por Declaracin. Un objeto se puede instanciar de una forma simple, declarando una variable del t ipo de la clase. En Ppal.h: #include "ObjGraf.h" En Ppal.cpp: Pulsando dos veces en OnCreate de la pestaa Events del editor de objetos de PpalF rm: //-------------------------------------------------void __fastcall TPpalFrm::FormCreate(TObject *Sender) { TObjGraf ObjGraf1(); TObjGraf ObjGraf2; } //-------------------------------------------------Aunque esta forma es posible, y bastante utilizada en la programacin de C ++ clsica, en C++ Builder se 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. Deb ido 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, nosotros no la utilizaremos. 2. Creacin Dinmica Es la forma habitual de crear objetos en C++ Builder, y se realiza mediante el o perador new . Cuando usamos new para instanciar un objeto, se usa una variable que referencie o apunte al nuevo objeto creado (de otra manera ste quedara totalmente inaccesible ). En definitiva, se requiere la declaracin previa de un puntero a objetos del ti po del que se va a crear. En Ppal.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 a Constructores 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 r ecupera los recursos (memoria) que ese objeto haba acaparado cuando se cre. La destruccin de objetos creados en tiempo de ejecucin con new se realiza mediante el operador delete. En Ppal.cpp: Pulsando dos veces en OnDestroy de la pestaa Events del editor de objetos de Ppal Frm: //-------------------------------------------------void __fastcall TPpalFrm::FormDestroy(TObject *Sender) { delete ObjGraf; } //-------------------------------------------------5.3. Encapsulamiento En la programacin clsica (lenguaje C, p.e.) existen datos y procedimientos que acta n sobre esos datos. No hay una relacin aparente entre datos y procedimientos (fun ciones) y esta relacin se establece de manera ms o menos pecisa de acuerdo a la pr ofesionalidad 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 explc itamente y se "encapsulan" en un objeto. La especificacin de las propiedades de u n 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 l os objetos de la clase TObjGraf: Figura 5.2. Propiedades y mtodos de los objetos de la clase TObjGraf. La declaracin de propiedades y mtodos de los objetos de la clase TObjGraf se reali za de la siguiente manera: En ObjGraf.h: //-------------------------------------------------class TObjGraf { public: int int TColor TPaintBox }; //-------------------------------------------------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 -> par a acceso a travs de un puntero. Como nosotros siempre creamos los objetos con new X; // Propiedades Y; Color; *PaintBox;

void Mostrar (void); // Mtodos

, y los referenciamos mediante un puntero, el operador de acceso que utilizaremo s es el operador -> En Ppal.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 si gnifican 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 co nstructores se pueden definir con un conjunto de argumentos arbitrario, pero no pueden devolver nada. Y los destructores no pueden recibir ni devolver ningn valo r. 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) c uando se crea dinmicamente con el operador new. Un destructor se ejecuta cuando e l objeto deja de existir: 1) porque su mbito acaba, 2) cuando se libera explcitame nte con el operador delete. En ObjGraf.h: class TObjGraf { ... // Constructor de objetos TObjGraf TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack, int _X=0, int _Y=0); // El destructor sera: ~TObjGraf (void); }; En ObjGraf.cpp: TObjGraf :: TObjGraf (TPaintBox * _PaintBox, TColor _Color, int _X, int _Y) { PaintBox = _PaintBox; Color = _Color; X = _X; Y = _Y; } En Ppal.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 m emoria dinmica adicional. De ser as, la tarea del destructor ser, bsicamente, libera r la memoria dinmica que ocupa el objeto que se va a destruir.

5.5. Herencia Cuando una clase hereda de otra, la clase derivada incorpora todos los miembros de la clase base adems 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 clase TObjGraf se van a construir dos nuevas clases, TCircu lo y TCuadrado, que derivan de TObjGraf. Esto significa que los objetos de estas clases tienen asociados las propiedades y mtodos de la clase base, TObjGraf, ade ms 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 clases TCirculo y TCuadrado heredan las propiedades y mtodos de l a clase TObjGraf.4 En ObjGraf.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 poner public, esto es as porque C++ per mite tambin la herencia private. Pero sta no se suele usar, por lo que nosotros su pondremos que slo existe la public. 5.5.1. Herencia de Constructores y Destructores (Inicializacin de Clases II) Los constructores y destructores de una clase no son heredadas automticamente por sus descendientes. Deberemos crear en las clases hijas sus propios constructore s y destructores. Es posible, no obstante, emplear los constructores de la clase base pero hay que indicarlo explcitamente. De ser as, es necesario saber: 1. que los constructores y destructores de las clases base son invocados au tomticamente antes que los constructores de las clases derivadas, y 2. que los destructores de las clases derivadas se invocan antes que los de las clases base. Para determinar con qu parmetros se llaman a los constructores de las clases base, se utiliza la lista de inicializacin. En ObjGraf.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); }; En ObjGraf.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 tanto no se pueden crear instancias de la misma. Una cl ase 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 un mtodo virtual p uro. Virtual: obliga a las clases derivadas a implementar ese mtodo. Puro: no pueden crearse instancias de esa clase. En ObjGraf.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); }; En ObjGraf.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 mtodo Mostrar en TObjGraf, como virtual puro, en lugar de o mitirlo? Fundamentalmente podemos considerar dos razones para usar mtodos virtual es puros: 1. Para obligar a que las clases descendientes los implementen. De esta for ma estamos seguros de que todas las clases descendientes no abstractas de TObjGr af poseen 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 en Ppal.cpp el gestor asociado al evento OnCreate del 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, en Ppal.cpp hay 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 tip o TCirculo y otros dos para referenciar a objetos de tipo TCuadrado: // Punteros a objetos de las clases derivadas. TCirculo *Cir1, *Cir2; TCuadrado *Cuad1, *Cuad2; 3. En segundo lugar, modificaremos la funcin FormCreate para que cree dos ob jetos 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 funcin FormDestroy para que elimine los obje tos 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 der ivadas, aunque no se visualizan en la ventana. Porqu? En ningn momento se ha llamad o al mtodo Mostrar() asociado a cada objeto. Para mostrar los objetos, basta con indicarlo en el gestor asociado al evento OnPaint del componente PaintBox: //-------------------------------------------------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 proyecto Ejemplo0. El resultado es el mostrado en la figura 5.4: 4 Figura 5.4. Resultado del proyecto Ejemplo.bpr. ________________________________________ ________________________________________ Ejercicio: Construir la clase TTriangulo y modificar el proyecto para que proporcione un re sultado similar al de la figura 5.5. Figura 5.5. Resultado del proyecto Ejemplo.bpr mostrando objetos de la clase TTr iangulo. ________________________________________ 5.5.3. Herencia Mltiple La herencia mltiple es el hecho de que una clase derivada se genere a partir de v arias 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 clase TCocheEnVenta derivan de las clases TProduc to y TVehiculo. ________________________________________ 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 h abr que estudiar cual es la mejor opcin. Por ejemplo, si quisieramos disear una clase (TMarco) que represente un marco (re presentado por un cuadrado y un crculo), podemos decidir distintas estrategias a la hora de llevarlo a cabo: Que herede de TCirculo y TCuadrado. Que herede de TObjGraf y contenga un objeto de la clase TCirculo y otro de TCuad rado. Que herede de TCirculo y contenga un objeto de la clase TCuadrado. Que herede de TCuadrado y contenga un objeto de la clase TCirculo. 5.6. Abstraccin Es la ocultacin de detalles irrelevantes o que no se desean mostrar. Podemos dist inguir 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 cab o. 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, ni camente 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 . En ObjGraf.h: //*************************************************/ // Definicion de la clase base TObjGraf //*************************************************/ class TObjGraf { private: int X; int Y; protected: // Pueden acceder los objetos de esta clase y sus descendientes. TColor Color; TPaintBox * PaintBox; public: // Pueden usarlas todas. // Puede acceder SOLO los objetos de esta clase.

// 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 clases TCirculo y TCuadrado para que sus propie dades Radio y Lado queden 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 en Ppal.cpp escribiramos: //-------------------------------------------------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 de C++, pero en C++ Builder existe otro adicional, que es el __published. No vamos a dar mucha importancia a este modificador, porque su uso est restringido al IDE. Cuando en u na clase veamos una seccin __published quiere decir que los miembros contenidos e n 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 d e un objeto, ya que si esto ocurriera podra peligrar la integridad del objeto. Ent onces 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 prop iedades y el usuario de la clase slo accede a travs de ellos. Esta es la tcnica clsi ca que se emplea en C++ 2. A travs de los mtodos y de las propiedades "virtuales". Esta tcnica es excl usiva 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 tradu cen en la llamada a un mtodo o en el acceso a una propiedad real. Es ms, si una pr opiedad virtual se usa para lectura (p.e. en la parte derecha de una asignacin) s e traduce en una accin diferente que si esa popiedad virtual se usa para escritur a. La accin que se produce cuando la propiedad virtual es de lectura se especific

a, sintcticamente, mediante la palabra reservada read mientras que si se usa para escritura se especifica con write. Veamos un ejemplo. En ObjGraf.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 void SetY virtual int virtual int (int _X); (int _Y); GetAncho (void) = 0; // Metodo virtual puro GetAlto (void) = 0; // Metodo virtual puro

protected: // Pueden acceder los objetos de esta clase y descendientes. TColor TPaintBox public: FColor; *PaintBox; // OJO: Se ha cambiado Color por FColor

// 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 __property __property __property __property }; Observaciones: Observar que la "antigua" propiedad X (resp. Y) se llama ahora FX (resp. FY). Ad ems, hay una propiedad virtual (pblica) llamada X (resp. Y). Estas propiedades estn declaradas en la clase TObjGraf lo que significa que sus descendientes tambin la s heredan. Si en Ppal.cpp se 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) X en realidad se a ccede a la propiedad (real) FX. La ltima instruccin, no obstante, povocara un error porque FX es una propiedad privada. Si se usara esta propiedad para escritura: Cir1->X = 100; En realidad es como si se hiciera int int TColor int int X Y Color Ancho Alto = = = = = {read=FX, write=SetX }; {read=FY, write=SetY }; {read=FColor, write=FColor}; {read=GetAncho }; {read=GetAlto };

Cir1->SetX(100); ya que cuando se accede para escritura a la propiedad (virtual) X en realidad se llama al mtodo SetX(). La ltima instruccin, no obstante, povocara un error porque S etX() es un mtodo privado. Al "redirigir" la escritura al mtodo SetX() podemos con trolar la validez del parmetro y corregir, en su caso, el valor: una ventaja adic ional. La propiedad virtual Color tiene asociado el mismo mtodo de acceso para lectura y escritura: devuelve o escribe, directamente, en la propiedad real FColor. Finalmente, observar que las propiedades virtuales Ancho y Alto no tienen asocia dos mtodos de acceso para escritura. Como hemos incorporado dos nuevos mtodos a la clase base TObjGraf (GetAncho() y G etAlto()) y stos se han declarado virtuales puros necesitamos instanciarlos en la s 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 (en ObjGraf.cpp) las funciones write de las propiedades virtuales X e Y: // 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 clase TObjGraf porque no se puede llamar a los mtodos virtuales puros de una propiedad virtual desde un constructo r de una clase base. En este caso, no se puede llamar a los mtodos virtuales puros (GetAncho() y GetAl to()) de las propiedades virtuales (Ancho y Alto) desde el constructor de la cla se base TObjGraf. En ObjGraf.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 proyecto Ejemplo1. 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, Cuad1 y Cuad2) se crean con sus correspondientes constructores, que en ltima instancia llaman al constructor de la clase base par a establecer los valores de las propiedades reales FX, FY, FColor y PaintBox. La consecuencia es que si se crea un crculo, por ejemplo, con coordenadas incorrect as no se corrigen ya que como el constructor de la clase base no puede utilizar la propiedad virtual X para escritura no se emplea el mtodo SetX().

Como demostracin, en la funcin FormCreate() de Ppal.cpp cambiar 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 funcin PaintBoxPaint() de Ppal.cpp aadiendo esta lnea al principio: if (Cir1->X < 0) Cir1->X = 100; Observar que la propiedad virtual X se usa para lectura y escritura. La lnea ante rior fija la coordenada X al 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 valor 0 en la coordenada X: este valor se h a fijado a travs del mtodo de escritura SetX(). 5.7. Polimorfismo Es demostrar comportamientos distintos segn la situacin. Puede darse de tres forma s 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 aplic ar cada uno de ellos en la situacin apropiada. Por ejemplo, podra sobrecargarse el constructor de la clase TObjGraf aadiendo un n uevo constructor de copia: En ObjGraf.h: TObjGraf (TObjGraf *ObjGraf); En ObjGraf.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 d istintas clases, el compilador no sabe qu tipo de objeto es al que realmente apun ta la variable (en tiempo de compilacin) por lo tanto hay retrasar el enlace a ti empo de ejecucin. El enlace dinmico es retrasar el enlace de una llamada a un mtodo (funcin) al tiemp o de ejecucin. Para ilustrar el polimorfismo, crearemos una nueva clase, TPelota, que derive de la clase TCirculo. Una pelota (un objeto de tipo TPelota, 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 cl ase TPelota. Sin embargo, en este momento nos interesa poner de manifiesto el po limorfismo, lo que conseguimos a travs del mtodo Mostrar() asociado a la clase TPe lota. Antes, modificamos la declaracin del mtodo Mostrar() de la clase TCirculo pa

ra obligar a sus descendientes a implementar su propio mtodo Mostrar(): basta con indicar que en el mtodo Mostrar() de la clase TCirculo es virtual. En ObjGraf.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 clase TPelota. Antes, por comodidad y claridad, definimos un tipo por enumeracin para la direccin del movimiento: En ObjGraf.h: // Tipo definido por enumeracion para la direccion de TPelota. Codificacion: /* NO 10 \ O 8 --/ 9 SO */ enum TDireccion {S=1, N=2, E=4, O=8, SE=5, NE=6, SO=9, NO=10}; La declaracin de la clase TPelota se har en ObjGraf.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: N NE 2 6 | / * --- 4 E | \ 1 5 S SE

// 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 = write= __property TDireccion Direccion = {read = write= FVelocidad, FVelocidad}; GetDireccion, SetDireccion};

}; La implementacin de los mtodos propios de la clase TPelota se har en ObjGraf.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 dife rentes mtodos con el mismo nombre: Mostrar(), que provocan diferentes acciones de pendiendo 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 tipo TObj Graf *. El polimorfismo se va a manifestar invocando a la funcin Mostrar() para c ada uno de esos objetos. En Ppal.cpp declararemos la variable global: TObjGraf **Objs; que se interpreta como: Objs es un puntero a una zona de memoria que contendr pun teros a objetos de tipo TObjGraf. As, en Ppal.cpp: //---------------------------------------------------------------------void __fastcall TPpalFrm::FormCreate(TObject *Sender) { Objs = new TObjGraf * [4]; Objs[0] Objs[1] Objs[2] Objs[3] } //---------------------------------------------------------------------void __fastcall TPpalFrm::FormDestroy(TObject *Sender) { for (int i=0; i<4; i++) delete Objs[i]; delete []Objs; } //---------------------------------------------------------------------void __fastcall TPpalFrm::PaintBoxPaint(TObject *Sender) { for (int i=0; i<4; i++) Objs[i]->Mostrar(); // POLIMORFISMO } //---------------------------------------------------------------------________________________________________ = = = = new new new new TCirculo TCuadrado TCirculo TPelota (PaintBox, (PaintBox, (PaintBox, (PaintBox, clBlack, clRed, clGreen, clYellow, 100, 200, 210, 120, 100, 150, 40, 70, 30); 45); 20); 25);

En este punto, el proyecto debe estar como se indica en el proyecto Ejemplo2. El resultado es el mostrado en la figura 5.6: Figura 5.6. Resultado del proyecto Ejemplo.bpr.

You might also like