View
213
Download
0
Embed Size (px)
DESCRIPTION
Classe: é uma especificação das características de um conjunto de objetos. Diz-se que um objeto é uma instância de uma classe. A POO é um paradigma de programação que se fundamenta nos conceitos de objeto e de classe. Começaremos definindo esses dois conceitos. 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 F ERNANDO E SPINOSA C OCIAN L UIS 117 P ROCESSAMENTO D IGITAL I I E NGENHARIA D E
Citation preview
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
116
55.. PPrrooggrraammaaççããoo OOrriieennttaaddaa aa
OObbjjeettooss eemm CC++++ 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.
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
117
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.
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
118
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 //--------------------------------------------------
55..11.. OO PPaarraaddiiggmmaa ddaa PPOOOO eemm CC++++ Existem quatro princípios básicos que qualquer sistema orientado a
objetos tem de incorporar, e que são esquematizados na Figura 5-1.
Encapsulamento
Abstração
Polimorfismo
Herança
POO
Figura 5-1 – Pilares da POO.
55..22.. CCrriiaaççããoo ee DDeessttrruuiiççããoo ddee OObbjjeettooss 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.
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
119
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; } //--------------------------------------------------
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
120
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; } //---------------------------------------------------------------------------
55..33.. EEnnccaappssuullaammeennttoo 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.
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
121
TObjGraf
Mostrar() Coordenada x
Coordenada y
Cor
PaintBox
Classe TObjGraf
unsigned int unX = 0
unsigned int unY = 0
TColor Cor = clBlue
TPaintBox PaintBox
void Mostrar (void)
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).
55..33..11.. AAcceessssoo aaooss MMeemmbbrrooss ddee uumm OObbjjeettoo 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.
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
122
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.
55..44.. CCoonnssttrruuttoorreess ee DDeessttrruuttoorreess 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
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
123
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.
55..55.. HHeerraannççaa 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.
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
124
TObjGraf
Mostrar() Coordenada x
Coordenada y
Cor
TCirculo
Raio
TQuadrado
Lado
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.
55..55..11.. HHeerraannççaa ddee CCoonnssttrruuttoorreess ee
DDeessttrruuttoorreess 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
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
125
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; } //---------------------------------------------------------------------------
55..55..22.. CCllaasssseess AAbbssttrraattaass 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 é
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
126
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:
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
127
� 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)
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
128
{ delete Cir1; delete Cir2; delete Quad1; delete 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:
EEXXEERRCCÍÍCCIIOO AADDIICCIIOONNAALL Construir a classe TTriangulo e modificar o projeto para que proporcione
um resultado similar ao da Figura 5-4.
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
129
Figura 5-4 - Resultado do proyecto POOEx.bpr mostrando objetos da classe TTriangulo.
55..55..33.. HHeerraannççaa MMúúllttiippllee 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.
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
130
� Herdar de TQuadrado e que contenha um objeto da classe TCirculo.
55..66.. AAbbssttrraaççããoo 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.
55..66..11.. RReessttrriiççõõeess ddee aacceessssoo eemm CC++++ 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); }; //---------------------------------------------------------------------------
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
131
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.
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
132
É 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.
55..66..22.. PPrroopprriieeddaaddeess VViirrttuuaaiiss 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 unX = {read=unFX, write=SetX}; __property unsigned int unY = {read=unFY, write=SetY};
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
133
__property TColor Cor = {read=FCor, write=FCor}; __property unsigned int unLargo = {read=GetLargura }; __property unsigned int unAlto = {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);}
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
134
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:
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
135
//--------------------------------------------------------------------------- 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.
55..77.. PPoolliimmoorrffiissmmoo 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.
55..77..11.. SSoobbrreeccaarrggaa ddee ffuunnççõõeess 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) {
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
136
PaintBox = ObjGraf->PaintBox; FCor = ObjGraf->Cor; unFX = ObjGraf->unFX; unFY = ObjGraf->unFY; } //---------------------------------------------------------------------------
55..77..22.. PPoolliimmoorrffiissmmoo nnaass ccllaasssseess ee
mmééttooddooss vviirrttuuaaiiss 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:
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
137
Em ObjGraf.h:
// Tipo definido por enumeração para a direção de TBola. Codificação: /* NO N NE 10 2 6 \ | / O 8 --- * --- 4 E / | \ 9 1 5 SO S 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;
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
138
} //--------------------------------------------------------------------------- // 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;
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
139
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] = new TCirculo (PaintBox, clBlue, 50, 50, 30); Objs[1] = new TCirculo (PaintBox, clRed, 210, 40, 70); Objs[2] = new TQuadrado (PaintBox, clGreen, 320, 150, 45); Objs[3] = new TQuadrado (PaintBox, 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.