L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
C
I
A
N
5. Programação Orientada a Objetos em C++ Nesta seção não se pretente mostrar a teoria completa da POO mas tão somente apresentar os conceitos necessários para uma correta programação usando o C++Builder. A POO é um paradigma de programação que se fundamenta nos conceitos de objeto e de classe. Começaremos definindo esses dois conceitos. Objeto: é uma entidade autônoma com uma funcionalidade concreta e bem definida. Classe: é uma especificação das características de um conjunto de objetos. Diz-se que um objeto é uma instância de uma classe. Os conceitos apresentados nesta seção serão ilustrados usando um exemplo que será completado aos poucos à medida que forem introduzidos novos conceitos. Este mesmo exemplo será usado mais adiante nas seções dedicadas ao tratamento de exceções e ã programação com threads. 1. Para iniciar, comece criando um novo projeto usando File + New Application. Salve a aplicação em uma nova pasta nomeando Unit1.cpp para Uprincipal.cpp e Project1.bpr para POOEx.bpr 2. Alterar o nome do quadro (Name = FrmPrincipal). Colocar nele um objeto PaintBox (aba System) com Name = PaintBox, Align = alTop. Deixar um espaço embaixo do PaintBox para colocar um botão.
3. Colocar um objeto Bevel de altura 4 e alinhado por cima (Align = alTop). A idéia é delimitar a parte inferior do objeto PaintBox.
116
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
4. Colocar um objeto BitBtn que permita terminar a execução do programa. O botão deverá estar centralizado horizontalmente na parte inferior do quadro. 5. Criar uma nova unidade cpp usando a opção de menu File + New + Unit. Salvar com o nome ObjGraf.cpp.
6. Quando se cria uma unidade cpp desta forma se crian em verdade dois arquivos, um com extensão .cpp e outro com extensão .h. Assim dispomos dos arquivos ObjGraf.h que conterá as declarações das classes com as que vamos trabalhar e ObjGraf.cpp que conterá as definições (implementações dos métodos) das mesmas. 7. Abra o arquivo ObjGraf.h. Para isto, no Editor de Códigos, clique com o botão direito do mouse acima do arquivo ObjGraf.cpp chamando o menu de contexto e escolha a opção Open Source/Header File CTRL+F6.
117
L
U
I
S
F
E
R
N
A
N
D
E
O
S
P
I
N
O
S
A
C
O
C
I
A
N
8. Para criar uma classe, colocar o seguinte código em ObjGraf.h. Notar que o nome da classe vai precedido por uma letra T, e embora isto não seja obrigatório, é recomendável o seu uso já que é uma convenção no C++Builder. Você pode usar também outras convenções próprias, como por exemplo, começar com a letra C: CObjGraf. No exemplo a continuação é definida uma classe mas não se criou nenhum objeto ainda. //-------------------------------------------------#ifndef ObjGrafH #define ObjGrafH // Definição da classe TObjGraf class TObjGraf {}; #endif //--------------------------------------------------
5.1. O Paradigma da POO em C++ Existem quatro princípios básicos que qualquer sistema orientado a objetos tem de incorporar, e que são esquematizados na Figura 5-1.
Abstração
Polimorfismo
Herança
Encapsulamento
POO
Figura 5-1 – Pilares da POO.
5.2. Criação e Destruição de Objetos Já foi dito que uma classe é nada mais (e nada menos) que uma especificação. Para poder usar a funcionalidade contida na mesma devem se instanciar as classes. A criação de objetos de uma classe pode ser feita por declaração explícita ou por criação dinâmica.
118
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
9. Um objeto pode ser instanciado de forma simples, declarando uma variável do tipo da classe. Por exemplo, no arquivo Uprincipal.cpp crie um gerenciador de eventos para o OnCreate (aba Events) e coloque na função criada o seguinte código: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender) { TObjGraf ObjGraf1(); TObjGraf ObjGraf2; } //---------------------------------------------------------------------------
10. Em Uprincipal.cpp acrescentar o arquivo de cabeçalho ObjGraf usando a opção de menu File + Include Unit Hdr.
No programa serão criados dois objetos de forma, ObjGraf1 e ObjeGraf2 que são da classe TObjGraf. Esta forma de instanciação é bastante usada na programação clássica usando C++, no entanto no C++ Builder é utilizada em raras ocasiões. Isto é por duas razões fundamentais: A duração dos objetos costuma ir além de uma simples função ou bloco. Devido ao enfoque de programação orientada à eventos, é comum que um objeto seja criado dentro de um gerenciadorde eventos e seja destruido em outro. Não é possível esta modalidade de criação com os objetos da VCL.
Uma outra forma de criar objetos mais apropriada no C++Builder é a criação dinâmica, realizada mediante o uso do operador new. Quando é usado o operador new para instanciar um novo objeto deve se usar uma variável que referencie ou aponte para o novo objeto criado (de outra forma esse ficaria totalmente inacessível). Assim, se requer de uma declaração prévia de um ponteiro para objetos do tipo da classe que vai se criar. Para instanciar um objeto de forma dinâmica alterar o código da unidade Uprincipal.cpp para parecer como segue: TObjGraf * ObjGraf; // Variável Global dentro da unidade .cpp. // ObjGraf é um ponteiro para objetos do tipo TObjGraf //-------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender){ ObjGraf = new TObjGraf; } //--------------------------------------------------
119
L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
C
I
A
N
A forma de estabelecer o estado inicial ou de destruir os componentes de um objetos serão estudados mais adiante na seção Construtores e Destrutores. Quando
se
utiliza
esta
forma
de
instanciação
de
classes
é
de
responsabilidade do programador a correta destruição dos objetos criados. Quando um objeto deixa de ser útil deve ser eliminado. Assim a aplicação recupera os recursos (memória) que o objeto tinha ocupado quando foi criado. A destruição dos objetos criados em tempo de execução com new é feita mediante o operador delete. No exemplo, crie um gerenciador de eventos do quadro para o evento OnDestroy (aba Events do Inspetor de Objetos) e escreva o código que segue em UPrincipal.cpp: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormDestroy(TObject *Sender) { delete ObjGraf; } //---------------------------------------------------------------------------
5.3. Encapsulamento Na programação clássica (linguagem C por exemplo) existem dados e procedimenos que atuam com esses dados. Não há uma relação aparente entre dados e procedimentos (funções) e esta relação se estabelece de forma mais ou menos precisa de acordo com a habilidade do programador. Em um objeto pode se distinguir dois aspectos bem diferenciados: Estado: que são as propriedades do objeto Comportamento: que são os métodos (funções) que o objeto pode executar.
Na POO os dados e os procedimentos processam esses dados esstão relacionados explicitamente e se “encapsulam” dentro do objeto. A especificação das propriedades de um objeto e os métodos de acesso se realiza na declaração da classe da qual o objeto foi instanciado. A Figura 5-2 esquematiza as propriedades e os métodos que serão associados aos objetos da classe TObjGraf.
120
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
Classe TObjGraf
Mostrar()
Coordenada x Coordenada y TObjGraf
unsigne d int unX = 0 unsigne d int unY = 0 TColor Cor = clBlue TPaintBox PaintBox
Cor voi d Mostrar (void)
PaintBox Figura 5-2 – Propriedades e métodos da classe TObjGraf e a sua representação em UML9.
A declaração das propriedades e métodos dos objetos da classe TObjGraf será realizada da seguinte maneira (em ObjGraf.h): //--------------------------------------------------------------------------class TObjGraf { public: unsigned int unX; // Propriedades unsigned int unY; TColor Cor; TPaintBox *PaintBox; void Mostrar (void); // Métodos }; //---------------------------------------------------------------------------
Observar no Explorador de Classes a classe TObjGraf e os seus componentes (métodos e propriedades).
5.3.1. Acesso aos Membros de um Objeto Para poder acessar os membros de um objeto se usam os operadores típicos de acesso a membros: o operador ponto (.) para referenciar diretamente o 9 A UML (Unified Modeling Language) é uma linguagem para especificação, documentação, visualização e desenvolvimento de sistemas orientados a objetos. Sintetiza os principais métodos existentes, sendo considerada uma das linguagens mais expressivas para modelagem de sistemas orientados a objetos. Por meio de seus diagramas é possível representar sistemas de softwares sob diversas perspectivas de visualização. Facilita a comunicação de todas as pessoas envolvidas no processo de desenvolvimento de um sistema gerentes, coordenadores, analistas, desenvolvedores - por apresentar um vocabulário de fácil entendimento.
121
L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
C
A
O
C
I
A
N
objeto e o operador seta (->) para acesso através de um ponteiro. Os membros dos objetos criados com o operador new (que são referenciados por ponteiros) devem ser acessados pelo operador seta (->). Para observar um exemplo de acesso, alterar a unidade Uprincipal.cpp no gerenciador do evento OnCreate para parecer como segue: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender) { ObjGraf = new TObjGraf; int ValorY; ObjGraf->unX = 5; ValorY = ObjGraf->unY; ObjGraf->Mostrar(); //Equivale a (*Obj).Mostrar(); } //---------------------------------------------------------------------------
Este código ainda não poderá ser executado, pois o método TObjGraf>Mostrar() ainda não foi definido. De qualquer forma, serve como exemplo de forma de acesso.
5.4. Construtores e Destrutores Os construtores e destrutores são métodos padronizados que permitem estabelecer o estado inicial e final de um objeto. Os construtores podem ser definidos com um conjunto arbitrário de argumentos, mas não podem retornar valor. Os destrutores são métodos que não podem receber nenhum tipo de argumento e não podem retornar nenhum valor. Os construtores devem ter o mesmo nome da classe e o destrutor também com a diferença de que este último deve vir precedido do caractere til (~). O construtor é executado quando se cria um novo objeto: 1) por declaração ou; 2) quando é criado dinamicamente com o operador new. Um destrutor é executado quando o objeto deixa de existir: 1) porque acaba o seu âmbito ou; 2) quando é liberado explicitamente da memória com o operador delete. Um exemplo de declaração de um construtor e destrutor para o objeto TObjGraf poderia ser: Em ObjGraf.h: class TObjGraf { ... // Construtor de objetos TObjGraf
122
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack, int _X=0, int _Y=0); // O destrutor poderia ser: ~TObjGraf (void); };
Em ObjGraf.cpp: //--------------------------------------------------------------------------TObjGraf::TObjGraf (TPaintBox * _PaintBox, TColor _Cor, int _X, int _Y) { PaintBox = _PaintBox; Cor = _Color; unX = _X; unY = _Y; } //---------------------------------------------------------------------------
Em UPrincipal.cpp: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender) { ObjGraf = new TObjGraf (PaintBox, clRed, 10, 10); } //---------------------------------------------------------------------------
Em geral não é necessário escrever um destrutor a não ser que o objeto requeira memória dinâmica adicional. Se for o caso, a tarefa do destrutor será basicamente liberar a memória dinâmica que ocupa o objeto que vai ser destruído.
5.5. Herança Quando uma classe herda de outra, a classe derivada incorpora todos os membros da classe base além dos membros próprios da mesma. A herança é uma ferramenta muito importante em muitos aspectos do desenvolvimento de aplicações: •
Organização do projeto
•
Reutilização de classes (próprias ou não)
•
Facilita a manutenção do código
Tomando como base a classe TObjGraf construiremos duas novas classes: TCirculo e TQuadrado, que derivam de TObjGraf. Isto significa que os objetos dessas classes terão associadas as propriedades e os métodos da classe base TObjGraf além dos seus próprios. A Figura 5-3 esquematiza o mecanismo de herança para as novas classes e as novas propriedades que se associam aos objetos das classes derivadas.
123
L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
Mostrar()
C
I
A
N
Coordenada x Coordenada y TObjGraf Cor
Raio
Lado TCirculo
TQuadrado
Figura 5-3 - As classes TCirculo e TQuadrado herdam as propriedades e métodos da classe TObjGraf.
Para ilustrar o exemplo, alterar as unidades como segue: Em ObjGraf.h: //--------------------------------------------------------------------------// Definição da classe derivada TCirculo // Deriva da classe base TObjGraf class TCirculo : public TObjGraf { public: unsigned int unRadio; // Propriedade exclusiva de TCirculo }; //--------------------------------------------------------------------------// Definição da classe derivada TQuadrado. // Deriva da classe base TObjGraf class TCuadrado : public TObjGraf { public: unsigned int unLado; // Propriedade exclusiva de TQuadrado }; //---------------------------------------------------------------------------
Antes do nome da classe base foi colocado o especificador de acesso public que define a forma em que os membros da clase base poderão ser acessados (ou não: Derivação public: os membros public da classe base são public na classe derivada; os membros protected permanecem protected e; os membros private permanecem private. Derivação protected: os membros public e protected da classe base são protected na classe derivada; os membros private permanecem private. Derivação private: os membros public e protected da classe base são private na classe derivada; os membros private permanecem private.
5.5.1. Herança de Construtores e Destrutores Os construtores e destrutores de uma classe não são herdados automaticamente pelas classes derivadas. Construtores e destrutores próprios devem ser criados nas classes derivadas. No entanto, é possível utilizar os
124
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
construtores da classe base porém isto deve ser indicado explicitamente. Assim, é útil saber que o construtor da classe base é invocado automaticamente antes que o construtor da classe derivada e que o destrutor da classe derivada se invoca antes que o da classe base. Para determinar com quais parâmetros se chamará o construtor da classe base, se utiliza uma lista de Inicialização. Para ilustrar no exemplo, alterar o código como segue: Em ObjGraf.h: //--------------------------------------------------------------------------// Definição da classe derivada TCirculo // Deriva da classe base TObjGraf class TCirculo : public TObjGraf { public: unsigned int unRadio; // Propriedade exclusiva de TCirculo // Método construtor TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlue, int _X=0, int _Y=0, int _Radio=1); }; //--------------------------------------------------------------------------// Definição da classe derivada TQuadrado. // Deriva da classe base TObjGraf class TCuadrado : public TObjGraf { public: unsigned int unLado; // Propriedade exclusiva de TQuadrado // Método construtor TQuadrado (TPaintBox * _PaintBox, TColor _Color=clBlue, int _X=0, int _Y=0, int _Lado=1); }; //---------------------------------------------------------------------------
Em ObjGraf.cpp: //--------------------------------------------------------------------------TCirculo::TCirculo (TPaintBox * _PaintBox, TColor _Color, int _X, int _Y, int _Radio) : TObjGraf (_PaintBox, _Color, _X, _Y) { unRaio = _Radio; } //--------------------------------------------------------------------------TQuadrado::TQuadrado (TPaintBox * _PaintBox, TColor _Color, int _X, int _Y, int _Lado) : TObjGraf (_PaintBox, _Color, _X, _Y) { unLado = _Lado; } //---------------------------------------------------------------------------
5.5.2. Classes Abstratas Uma classe abstrata é uma classe que não está completamente especificada (que possui métodos sem implementar), e portanto não se podem criar instâncias de si mesmas. Uma classe abstrata se usa para servir de classe base a outras classes. Na terminologia C++ diz-se que uma classe abstrata é
125
L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
C
I
A
N
aquela que possui pelo menos um método virtual puro. Os métodos virtuais obrigam às classes derivadas a implementar esse método. O termo puro significa que não podem ser criadas instâncias diretamente desta classe (somente derivadas). Para visualizar esta funcionalidade, alterar o código do exemplo como segue: Em ObjGraf.h: //--------------------------------------------------------------------------class TObjGraf { public: … virtual void Mostrar(void) = 0; // Método virtual puro … }; //--------------------------------------------------------------------------// Definição da classe derivada TCirculo // Deriva da classe base TObjGraf class TCirculo : public TObjGraf { public: … // Instanciação do método virtual puro da clase TObjGraf void Mostrar (void); }; //--------------------------------------------------------------------------// Definição da classe derivada TQuadrado. // Deriva da classe base TObjGraf class TQuadrado : public TObjGraf { public: … // Instanciação do método virtual puro da clase TObjGraf void Mostrar (void); }; //---------------------------------------------------------------------------
Em ObjGraf.cpp: //--------------------------------------------------------------------------void TCirculo::Mostrar(void) { PaintBox->Canvas->Pen->Color = Cor; PaintBox->Canvas->Brush->Color = Cor; PaintBox->Canvas->Ellipse(unX, unY, unX + unRaio * 2, unY + unRaio * 2); } //--------------------------------------------------------------------------void TQuadrado::Mostrar(void) { PaintBox->Canvas->Pen->Color = Cor; PaintBox->Canvas->Brush->Color = Cor; PaintBox->Canvas->Rectangle(unX, unY, unX + unLado, unY + unLado); } //---------------------------------------------------------------------------
Por que se especifica o método Mostrar() em TObjGraf como virtual puro no lugar de simplesmente omiti-lo? Fundamentalmente podem ser consideradas duas razões principais para usar métodos virtualmente puros:
126
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
Para obrigar que as classes derivadas os implementem. Desta forma estamos seguros que todas as classes descendentes não abstratas de TObjGraf possuem o método e que poderão ser invocados com segurança. Para evitar que se possam criar instâncias da classe abstrata.
Neste estado, se o programa tentar ser executado aparecerá uma mensagem de erro: [C++ Error] UPrincipal.cpp(24): E2352 Cannot create instance of abstract class 'TObjGraf'
Não se pode criar uma instância de uma classe abstrata. Mas por que acontece este erro? Lembrar que em UPrincipal.cpp o gerenciador associado ao evento OnCreate do quadro possui a seguinte declaração que deve ser apagada: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender) { ObjGraf = new TObjGraf (PaintBox, clRed, 10, 10); } //---------------------------------------------------------------------------
Apagar também o código colocado no destrutor: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormDestroy(TObject *Sender) { delete ObjGraf; } //---------------------------------------------------------------------------
A continuação criaremos os objetos das classes derivadas. Inicialmente apagar a declaração da variável global em UPrincipal.cpp: TObjGraf * ObjGraf; // Variável Global dentro da unidade .cpp. // ObjGraf é um ponteiro para objetos do tipo TObjGraf
No lugar dessa declarar quatro ponteiros, dois para referenciar a objetos do tipo TCirculo e outros dois para referenciar a objetos do tipo TQuadrado (ainda em UPrincipal.cpp). // Ponteiros para objetos das classes derivadas. TCirculo *Cir1, *Cir2; TQuadrado *Quad1, *Quad2;
A continuação modificar a função FormCreate para criar dois objetos de cada classe referenciados para os ponteiros declarados anteriormente. //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender) { Cir1 = new TCirculo (PaintBox, clBlue, 50, 50, 30); Cir2 = new TCirculo (PaintBox, clRed, 210, 40, 70); Quad1 = new TQuadrado (PaintBox, clGreen, 320, 150, 45); Quad2 = new TQuadrado (PaintBox, clWhite, 190, 30, 40); } //---------------------------------------------------------------------------
Finalmente, modificar a função FormDestroy para eliminar os objetos criados no fechamento da janela. //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormDestroy(TObject *Sender)
127
L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
C
I
A
N
{ delete delete delete delete
Cir1; Cir2; Quad1; Quad2;
} //---------------------------------------------------------------------------
Ao executar o programa se criam e destrói os objetos das classes derivadas embora não sejam visualizados na janela. Por que? Em nenhum momento foi chamado o método Mostrar() associado a cada objeto. Para mostrar os objetos basta com indicarlo no gerenciador associado ao evento OnPain do componente PaintBox. Adicione a esse gerenciador o seguinte código: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::PaintBoxPaint(TObject *Sender) { Cir1->Mostrar(); Cir2->Mostrar(); Quad1->Mostrar(); Quad2->Mostrar(); } //---------------------------------------------------------------------------
Neste ponto o projeto ao ser executado deverá aparecer da seguinte forma:
EXERCÍCIO ADICIONAL Construir a classe TTriangulo e modificar o projeto para que proporcione um resultado similar ao da Figura 5-4.
128
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
Figura 5-4 - Resultado do proyecto POOEx.bpr mostrando objetos da classe TTriangulo.
5.5.3. Herança Múltiple A herança múltiple é o fato de que uma classe derivada seja gerada a partir de várias classes base. Para entender melhor o fato, considerar que em uma aplicação para uma concessionária de automóveis pode existir a seguinte hierarquia de classes: class TProduto { unsigned int unPreco; ... }; class TVeiculo { unsignede int NumRodas; ... }; class TAutoEmVenda : public TProduto, public TVeiculo { ... };
Observar que os objetos da classe TAutoEmVenda derivam das classes TProduto e TVeiculo. Existem duas formas para que uma classe tire vantagem de outra, uma é a herança e outra é que a classe contenha um objeto da outra classe. Nenhuma das duas possibilidades pode ser considerada melhor que a outra, em cada caso em particular terá que se estudar qual a melhor opção. Por exemplo, se desejarmos criar uma classe TMoldura que representa a moldura de um quadro que pssa representar tanto um quadrado quanto um círculo, pode se decidir por diferentes estratégias na hora de implementar a classe: Herdar de TCirculo e TQuadrado. Herdar de TObjGraf e conter um objeto do tipo TCirculo e um outro do tipo TQuadrado. Herdar de TCirculo e que contenha um objeto da classe TQuadrado.
129
L
U
I
F
S
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
C
I
A
N
Herdar de TQuadrado e que contenha um objeto da classe TCirculo.
5.6. Abstração Abstração é o ocultamento de detalhes irrelevantes que não se desejam mostrar. Pode se distingüir em uma classe dois aspectos do ponto de vista da abstração: Interface: é o que se pode visualizar e usar de um objeto. Implementação: é como se leva a cabo a sua funcionalidade.
Resumindo, nos interessa saber o que nos oferece um objeto, e não como ele faz isto acontecer.
5.6.1. Restrições de acesso em C++ Na linguagem C++ pode se especificar o acesso aos membros de uma classe usando os seguintes especificadores de acesso: public: interface da classe. private: implementação da classe. protected: implementação da familia.
Esses especificadores não modificam a forma de acesso e nem o comportamento, no entanto controlam desde onde se podem usar os membros da classe. public: desde qualquer lugar. private: desde os métodos da classe. protected: desde os métodos da clase e desde os métodos das classes derivadas.
Para ilustrar, alterar as seguintes linhas no projeto POOEx.bpr: Em ObjGraf.h: //--------------------------------------------------------------------------class TObjGraf { private: unsigned int unX; // Propriedades unsigned int unY; protected: TColor Cor; TPaintBox *PaintBox; public: virtual void Mostrar(void) = 0; // Método virtual puro // Construtor de objetos TObjGraf TObjGraf (TPaintBox *_PaintBox, TColor _Cor=clBlue, int _X=0, int _Y=0); // O destrutor seria: ~TObjGraf (void); }; //---------------------------------------------------------------------------
130
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
Da mesma forma, alterar as classes TCirculo e TQuadrado para que as suas propriedades unRaio e unLado fiquem protegidas e os seus métodos ainda fiquem públicos. //--------------------------------------------------------------------------// Definição da classe derivada TCirculo // Deriva da classe base TObjGraf class TCirculo : public TObjGraf { protected: unsigned int unRaio; // Propriedade exclusiva de TCirculo public: // Método construtor TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlue, int _X=0, int _Y=0, int _Radio=1); // Instanciação do método virtual puro da clase TObjGraf void Mostrar (void); }; //--------------------------------------------------------------------------// Definição da classe derivada TQuadrado. // Deriva da classe base TObjGraf class TQuadrado : public TObjGraf { protected: unsigned int unLado; // Propriedade exclusiva de TQuadrado public: // Método construtor TQuadrado (TPaintBox * _PaintBox, TColor _Color=clBlue, int _X=0, int _Y=0, int _Lado=1); // Instanciação do método virtual puro da clase TObjGraf void Mostrar (void); }; //---------------------------------------------------------------------------
Se em UPrincipal.cpp formos escrever: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender) { … Cir1->Mostrar(); // pode … Cir1->unX = 10; // Não pode porque unX é private. } //---------------------------------------------------------------------------
Aqui acontecerá o seguinte erro: [C++ Error] UPrincipal.cpp(34): E2247 'TObjGraf::unX' is not accessible
Os três especificadores de acesso são próprios da linguagem C++, no entanto o C++Builder possui um outro especificador adicional denominado __published. Não vai se dar muita importância a este especificador porque o seu uso está restrito ao IDE. Quando em uma classe vejamos uma seção __published que dizer que os membros contidos na mesma são mantidos automaticamente pelo IDE e não devemos modificar nada nesta seção sob pena de obter resultados imprevisíveis.
131
L
U
I
F
S
E
R
N
A
N
D
O
E
S
P
I
N
O
S
C
A
O
C
I
A
N
É uma boa prática de programação não permitir o acesso público às propriedades de um objeto, já que isto pode colocar em perigo a sua integridade. Então, como se pode alterar o estado de um objeto desde o exterior? Oferecendo métodos (públicos) que se encarregem de modificar as propriedades (privadas). Desta maneira são os métodos que acessam às propriedades e o usuário da classe somente tem acesso através deles. Esta é a técnica clássica que se utiliza em C++. Através dos métodos e propriedades “virtuais”. Esta técnica é exclusiva do C++Builder e será descrita na próxima seção.
5.6.2. Propriedades Virtuais São propriedades definidas mediante métodos de leitura (read) e/ou escrita (write). Chama-se virtuais porque realmente não existem. O usuário da classe usa estas propriedades como se fossem propriedades reais e em última instância se traduzem na chamada a um método ou no acesso da propriedade real. Mais ainda, se uma propriedade virtual é usada para leitura (por exemplo na parte direita de uma atribuição) se traduz em uma ação diferente que se essa propriedade virtual é usada para a escrita. A ação se produz quando a propriedade virtual é de leitura, especificada sintaticamente mediante a palavra reservada read, enquanto que se for usada para escrita se especifica com write. Veja a alteração no projeto de exemplo, para ilustrar o uso de propriedades virtuais. Em ObjGraf.h: //--------------------------------------------------------------------------class TObjGraf { private: unsigned int unFX; // foram alterados os nomes unsigned int unFY; // de unX para unFX e unY para unFY void SetX(int _X); void SetY(int _Y); virtual int GetLargura (void) = 0; // Método virtual puro virtual int GetAltura (void) = 0; // Método virtual puro protected: TColor FCor; // mudou de Cor para FCor TPaintBox *PaintBox; public: virtual void Mostrar(void) = 0; // Método virtual puro // Construtor de objetos TObjGraf TObjGraf (TPaintBox *_PaintBox, TColor _Cor=clBlue, int _X=0, int _Y=0); // O destrutor seria: ~TObjGraf (void); // Novas Formas de Acesso com propriedades virtuais. __property unsigned int __property unsigned int
unX unY
= =
132
{read=unFX, {read=unFY,
write=SetX}; write=SetY};
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
__property TColor __property unsigned int __property unsigned int
C
E
S
S
A
M
E
Cor unLargo unAlto
N
T
O
= = =
D
I
G
I
T
A
L
I I
{read=FCor, write=FCor}; {read=GetLargura }; {read=GetAltura };
}; //---------------------------------------------------------------------------
Observar que a antiga propriedade unX (e unY) teve o nome alterado para unFX (e unFY). Além disso há uma propriedade virtual (pública) chamada unX (e unY). Essas propriedades estão declaradas na classe TObjGraf o que significa que as suas descendentes também a herdarão. Se em UPrincipal.cpp se usasse uma propriedade para leitura: int CX = Cir1->unX;
é a mesma coisa que: int CX = Cir1->unFX;
já que quando se acessa para letura a propriedade virtual unX na realidade se acessa a propriedade real unFX. A última instrução, no entanto, provocaria um erro porque a propriedade unFX foi declarada como privada. Se a propriedade em questão for usada para escrita: Cir1->X = 100;
Na realidade é como se fizesse: Cir1->SetX(100);
Já que quando se acessa para escrita a propriedade virtual unX, na realidade se chama o método SetX(); A última instrução, entretanto, provocaria um erro pois SetX() é um método privado. Ao redirecionar a escrita para o método SetX() pode se controlar a validade do parâmetro e corrigir, se for o caso, o valor, o que proporciona uma vantagem adicional. A propriedade virtual Cor possui o mesmo método associado tanto para leitura quanto para escrita: retorna o que escreve, diretamente na propriedade real FCor. Finalmente, observar que as propriedades virtuais unLargo e unAlto não possuem métodos de acesso para escrita. Por esses dois métodos terem sido declarados virtuais puros precisa-se instanciá-los nas classes derivadas. Ainda em ObjGraf.h: //--------------------------------------------------------------------------// Definição da classe derivada TCirculo // Deriva da classe base TObjGraf class TCirculo : public TObjGraf { protected: unsigned int unRaio; // Propriedade exclusiva de TCirculo inline int GetLargura (void) {return(unRaio*2);} inline int GetAltura (void) {return(unRaio*2);}
133
L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
C
I
A
N
public: // Método construtor TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlue, int _X=0, int _Y=0, int _Radio=1); // Instanciação do método virtual puro da clase TObjGraf void Mostrar (void); }; //--------------------------------------------------------------------------// Definição da classe derivada TQuadrado. // Deriva da classe base TObjGraf class TQuadrado : public TObjGraf { protected: unsigned int unLado; // Propriedade exclusiva de TQuadrado inline int GetLargura (void) {return(unLado);} inline int GetAltura (void) {return(unLado);} public: // Método construtor TQuadrado (TPaintBox * _PaintBox, TColor _Color=clBlue, int _X=0, int _Y=0, int _Lado=1); // Instanciação do método virtual puro da clase TObjGraf void Mostrar (void); }; //---------------------------------------------------------------------------
Agora, adicionar em ObjGraf.cpp as funções write das propriedades virtuais unX e unY: //--------------------------------------------------------------------------// Funções de escrita das propriedades virtuais unX e unY void TObjGraf::SetX(unsigned int _X) { if (_X < 0) // Coordenada negativa unFX = 0; // Ajustar para a margem esquerda else if (_X > (PaintBox->Width - unLargo)) // Alta demais unFX = PaintBox->Width - unLargo; // Ajustar para a margem direita else unFX = _X; // Correto: escrever sem modificar } //--------------------------------------------------------------------------void TObjGraf::SetY(unsigned int _Y) { if (_Y < 0) // Coordenada negativa unFY = 0; // Ajustar a margem superior else if (_Y > (PaintBox->Height - unAlto)) // alto demais unFY = PaintBox->Height - unAlto; // Ajustar para a margem inferior else unFY = _Y; // Correto: escrever sem modificar } //---------------------------------------------------------------------------
É importante notar que foi alterado o construtor da classe TObjGraf porque não se pode chamar os métodos virtuais puros de uma propriedade virtual desde um construtor de uma classe base. Neste caso, não se pode chamar os métodos virtuais puros (GetLargura() e GetAltura()) das propriedades virtuais (unLargo e unAlto) desde o construtor da classe base TObjGraf. Em ObjGraf.cpp:
134
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
//--------------------------------------------------------------------------TObjGraf::TObjGraf (TPaintBox * _PaintBox, TColor _Color, int _X, int _Y) { PaintBox = _PaintBox; FCor = _Color; unFX = _X; unFY = _Y; } //---------------------------------------------------------------------------
Neste ponto o projeto deve estar funcionando como antigamente.
5.7. Polimorfismo O Polimorfismo é o fato de demonstrar comportamentos distintos segundo a situação. Pode se dar de três formas diferentes: Funções: sobrecarga. Classes: é ao que se refere normalmente o conceito de polimorfismo. Enlace dinámico: métodos virtuais.
5.7.1. Sobrecarga de funções Ocorre quando em uma classe existem dois ou mais métodos com o mesmo nome mas com distintas listas de parâmetros. O compilador os considera como métodos distintos e aplica cada um deles na situação apropriada. Por exemplo, pode se sobrecarregar o construtor da classe TObjGraf adicionando um novo construtor de cópia: Em ObjGraf.h: class TObjGraf { … public: … // Construtor de objetos TObjGraf TObjGraf (TPaintBox *_PaintBox, TColor _Cor=clBlue, int _X=0, int _Y=0); TObjGraf (TObjGraf *ObjGraf); // sobrecarga de construtor … };
Em ObjGraf.cpp: //--------------------------------------------------------------------------TObjGraf::TObjGraf (TPaintBox * _PaintBox, TColor _Color, int _X, int _Y) { PaintBox = _PaintBox; FCor = _Color; unFX = _X; unFY = _Y; } //--------------------------------------------------------------------------TObjGraf::TObjGraf (TObjGraf *ObjGraf) {
135
L
U
I
S
F
E
R
N
A
N
D
O
E
S
P
I
N
O
S
A
C
O
C
I
A
N
PaintBox = ObjGraf->PaintBox; FCor = ObjGraf->Cor; unFX = ObjGraf->unFX; unFY = ObjGraf->unFY; } //---------------------------------------------------------------------------
5.7.2. Polimorfismo nas classes e métodos virtuais Uma classe pode se comportar como qualquer das suas antecessoras (por exemplo, na atribuição). Como há variáveis (ponteiros) que podem conter objetos de distintas classes, o compilador não sabe qual tipo de objeto é o realmente apontado pelo ponteiro (em tempo de compilação) por tanto esta definição deve ser deixada para o tempo de execução. O enlace dinâmico é atrasar o enlace de uma chamada a um método (função) em tempo de execução. Para ilustrar o polimorfismo criaremos uma nova classe TBola que deriva de TCirculo. Uma bola (objeto do tipo TBola para ser mais preciso) é um círculo que possui a capacidade de movimento. Para implementar o movimento de uma bola precisamos incorporar novas propriedades e métodos próprios à classe TBola. No entanto, neste momento nos interessa colocar de manifesto o polimorfismo, fato que pode ser conseguido através do método Mostrar() associado à classe TBola. Antes, modificaremos a declaração do método Mostrar() da classe TCirculo para obrigar a suas descendentes a implementar o seu próprio método Mostrar(). Para isto basta indicar que o método Mostrar() da classe TCirculo é virtual. Em ObjGraf.h: //--------------------------------------------------------------------------// Definição da classe derivada TCirculo // Deriva da classe base TObjGraf class TCirculo : public TObjGraf { … public: … // // // // //
Instanciação do método virtual puro da clase TObjGraf Agora o método Mostrar() é declarado virtual, embora não seja puro: 1) Por ser virtual: qualquier classe que derive de TCirculo deve ter o seu própiro método Mostrar(), 2) Por não ser puro: pode se chamar este método com objetos TCirculo.
virtual void Mostrar (void); }; //---------------------------------------------------------------------------
Agora centraremos o foco na nova classe TBola. Antes, por comodidade e clareza, definiremos um tipo enum para a direção do movimento:
136
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
Em ObjGraf.h: // Tipo definido por enumeração para a direção de TBola. Codificação: /* NO 10 \ O 8 --/ 9 SO
N 2 | * | 1 S
NE 6 / --- 4 E \ 5 SE
*/ enum TDirecao {S=1, N=2, E=4, O=8, SE=5, NE=6, SO=9, NO=10};
A declaração da classe TBola se fará em ObjGraf.h: //--------------------------------------------------------------------------// Definição da classe derivada TBola. // TBola deriva da classe TCirculo, que na sua // vez deriva da classe base TObjGraf class TBola: public TCirculo { private: int FDirX; // Dir. no eixo X int FDirY; // Dir. no eixo Y int FVelocidade; // Velocidade de movimento void SetDirecao (EnDirecao _Direcao); EnDirecao GetDirecao (void); public: // Construtores TBola (TPaintBox *_PaintBox, TColor _Color=clBlack, int _X=0, int _Y=0, int _Radio=1, EnDirecao _Direcao=SE, int _Velocidade=5); // Outros métodos void Mostrar (void); void Apagar (void); void Mover (void); __property int Velocidade = {read = FVelocidade, write= FVelocidade}; __property EnDirecao Direcao = {read = GetDirecao, write= SetDirecao}; }; //---------------------------------------------------------------------------
A implementação dos métodos próprios da classe TBola se fará em ObjGraf.cpp: //--------------------------------------------------------------------------// Métodos associados à classe derivada TBola. // TBola deriva da classe TCirculo, que na sua // vez deriva da classe base TObjGraf TBola::TBola (TPaintBox *_PaintBox, TColor _Color, int _X, int _Y, int _Raio, EnDirecao _Direcao, int _Velocidade) : TCirculo (_PaintBox, _Color, _X, _Y, _Raio) { Direcao = _Direcao; Velocidade = _Velocidade;
137
L
U
I
F
S
E
R
N
A
N
D
O
E
S
P
I
N
O
S
C
A
O
C
I
A
N
} //--------------------------------------------------------------------------// Instanciação do método virtual puro da classe TObjGraf // e virtual em TCirculo. void TBola::Mostrar (void) { PaintBox->Canvas->Pen->Color = clBlack; // Observar la diferencia PaintBox->Canvas->Brush->Color = Cor; PaintBox->Canvas->Ellipse(unX, unY, unX+unRaio*2, unY+unRaio*2); } //--------------------------------------------------------------------------// Outras funções próprias de TBola void TBola::Apagar (void) { PaintBox->Canvas->Pen->Color = PaintBox->Color; PaintBox->Canvas->Brush->Color = PaintBox->Color; PaintBox->Canvas->Ellipse(unX, unY, unX+unRaio*2, unY+unRaio*2); } //--------------------------------------------------------------------------void TBola::Mover (void) { Apagar (); unX += FDirX * Velocidade; unY += FDirY * Velocidade; Mostrar (); } //--------------------------------------------------------------------------void TBola :: SetDirecao (EnDirecao _Dir) { FDirY = (_Dir & 1) ? +1 : ((_Dir & 2) ? -1 : 0); FDirX = (_Dir & 4) ? +1 : ((_Dir & 8) ? -1 : 0); } //--------------------------------------------------------------------------EnDirecao TBola::GetDirecao (void) { EnDirecao _Dir; _Dir = (EnDirecao) ((FDirY == +1) ? 1 : ((FDirY == -1 ) ? 2 : 0)); _Dir = (EnDirecao) (_Dir + (FDirX == +1) ? 4 : ((FDirX == -1 ) ? 8 :0)); return (_Dir); } //---------------------------------------------------------------------------
Finalmente, para ilustrar o polimorfismo nos embassamos na existência de diferentes métodos com o mesmo nome Mostrar() que provoca diferentes ações dependendo do tipo de objeto a que se aplica. A seguir vamos criar dinâmicamente quatro objetos de classes diferentes. Esses objetos serão referenciados (mediante ponteiros) desde um vetor de objetos do tipo TObjGraf*. O polimorfismo vai se manifestar invocando a função Mostrar() para cada um desses objetos. Na unidade UPrincipal.cpp declarar a variável global: TCirculo
*Cir1,
*Cir2;
138
E
N
G
E
N
H
A
R
I
A
D
E
P
R
O
C
E
S
S
A
M
E
N
T
O
D
I
G
I
T
A
L
I I
TQuadrado *Quad1, *Quad2; TObjGraf **Objs;
A linha acima se interpreta como: Objs é um ponteiro para uma região da memória que conterá ponteiros do tipo TObjGraf. Assim, em UPrincipal.cpp: //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormCreate(TObject *Sender) { Objs = new TObjGraf * [4]; Objs[0] Objs[1] Objs[2] Objs[3]
= = = =
new new new new
TCirculo TCirculo TQuadrado TQuadrado
(PaintBox, (PaintBox, (PaintBox, (PaintBox,
clBlue, 50, 50, 30); clRed, 210, 40, 70); clGreen, 320, 150, 45); clWhite, 190, 30, 40);
} //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::FormDestroy(TObject *Sender) { for (int i=0; i<4; i++) delete Objs[i]; delete []Objs; } //--------------------------------------------------------------------------void __fastcall TFrmPrincipal::PaintBoxPaint(TObject *Sender) { for (int i=0; i<4; i++) Objs[i]->Mostrar(); // POLIMORFISMO } //---------------------------------------------------------------------------
Neste ponto o projeto deve aparecer como antigamente.
139