do classes e objetos no delphi

Upload: manu-januario

Post on 09-Jul-2015

72 views

Category:

Documents


0 download

TRANSCRIPT

Apresentando Classes e ObjetosClasse e objeto so dois termos normalmente usados em Object Pascal e em outras linguagens de POO. Entretanto, como eles so freqentemente mal-utilizados, vamos garantir que concordamos com suas definies. Uma classe um tipo de dados definido pelo usurio, que tem um estado (sua representao) e algumas operaes (seu comportamento). Uma classe possui alguns dados internos e alguns mtodos, na forma de procedimentos ou funes, e normalmente descreve as caractersticas genricas e o comportamento de vrios objetos semelhantes. Um objeto uma instncia de uma classe ou uma varivel do tipo de dados definido pela classe. Os objetos so entidades reais. Quando o programa executado, os objetos ocupam memria para sua representao interna. O relacionamento entre objeto e classe o mesmo que o existente entre varivel e tipo. Para declarar um novo tipo de dados class no Object Pascal, com alguns campos de dados locais e alguns mtodos, use a seguinte sintaxe: Type TDate = Class Month, Day , Year : Integer; Procedure SetValue (m,d,y:integer); Function LeapYear :Boolean; End; A funo e o procedimento declarados anteriormente devem ser completamente definidos na parte referente implementao da mesma unidade, incluindo a declarao da classe. Voc pode deixar o Delphi gerar um esqueleto da definio dos mtodos, usando o recurso Class Completion do editor (basta pressionar Ctrl + C enquanto o cursor estiver dentro da definio de classe). Voc pode dizer que os mtodos fazem parte da classe TDate colocando um prefixo classe-nome (usando um ponto entre eles), como no cdigo a seguir: Procedure TDate.SetValue (m,d,y : integer); Begin Month:=m; Day:=d; Year:=y; End; Function TDate.LeapYear:Boolean; Begin //chama isleapyear em sysUtils.pas Result:=IsLeapYear (Year); End;

Uma vez que a classe tenha sido definida, podemos criar um objeto e us-lo, como segue: Var ADay: TDate; Begin //cria ADay:=TDate.Create; //usa ADay.SetValue (1,1,2000) ; If ADay.LeapYear then ShowMessage (Leap Year: + IntToStr (ADay.Year)); //destroy ADay.Free; End; A notao usada no nada incomum, mas ela poderosa. Podemos escrever uma funo complexa (como LeapYear) e depois acessar seu valor para cada objeto TDate, como se ele fosse um tipo de dados primitivo. Observe que ADay.LeapYear uma expresso semelhante a ADay.Year, embora a primeira seja uma chamada de funo e a segunda seja um acesso direto aos dados. Conforme veremos no prximo capitulo, a notao usada pelo Object Pascal para acessar propriedades , novamente, a mesma.

Modelo de referencia a Objeto do DelphiEm algumas linguagens de POO, declarar uma varivel de um tipo de classe cria uma instancia dessa classe. Em vez disso, o Object Pascal baseado em um modelo de referncia a objeto. A idia que cada varivel de um tipo de classe, como ADay no trecho de cdigo anterior, no contem o valor de objeto. Em vez disso, ela contm uma referncia, ou um ponteiro, para indicar a posio de memria onde o objeto foi armazenado. O nico problema dessa estratgia que, quando declara uma varivel, voc no cria um objeto na memria, mas apenas reserva a posio e memria para uma referencia a um objeto. As instancias objeto devem ser criadas manualmente, pelo menos para os objetos das classes que voc define. As instancias de um componente que voc coloca em um formulrio so construdas automaticamente pelo Delphi. Para criar uma instancia de um objeto, podemos chamar seu mtodo Create, que um construtor. Conforme voc pode ver no ultimo trecho de cdigo, o construtor aplicado classe e no ao objeto. De onde vem o mtodo Create? Trata-se de um construtor da classe TObject, a partir da qual todas as outras classes herdam. Uma vez que voc tenha criado um objeto e tiver acabado de us-lo, precisa desfazer-se dele (para evitar o preenchimento da memria que voc no precisa mais, o que causa o que conhecido como estouro de memria). Isso pode ser feito chamando-se o mtodo Free(outro mtodo da classe TObject), como demonstrado na listagem

anterior. Desde que voc crie objetos quando precisar deles e os libere quando terminar de us-los, o modelo de referencia a objeto funcionar sem problemas.

Private, Protected e PublicUma classe pode ter qualquer volume de dados e qualquer numero de mtodos. Entretanto para uma boa estratgia orientada a objetos, os dados devem ficar ocultos, ou encapsulados, dentro da classe que os est usando. Quando voc acessa uma data, por exemplo, no faz sentido mudar apenas o valor do dia. Na verdade, mudar o valor do dia poderia resultar em uma data invlida, como 30 de fevereiro. Usar mtodos para acessar a representao interna de um objeto limita o risco de gerao de situaes errneas, pois os mtodos podem verificar se a data vlida e se recusar a modificar o novo valor, caso no seja. O encapsulamento importante, pois ele permite que o escritor de classe modifique a representao interna em uma verso futura. O conceito de encapsulamento bastante simples: basta considerar uma classe como uma caixa-preta com uma pequena parte visvel. A parte visvel, chamada interface da classe, permite que outras partes de um programa acessem e usem os objetos dessa classe. Entretanto, quando voc usa os objetos, a maior parte de seu cdigo fica oculta. Raramente voc sabe quais dados internos o objeto possui e normalmente no tem meios de acessar os dados diretamente. claro que voc dever usar mtodos para acessar os dados, os quais ficam isolados de acesso noautorizado. Essa a estratgia orientada a objetos de um conceito de programao clssico, conhecido como ocultamento de informaes. O Object Pascal possui trs especificadores de acesso: private, protected e public. Um quarto especificador, published, ser discutido no prximo capitulo. Aqui esto os trs bsicos: A diretiva private (privado) denota campos e mtodos de um classe que no so acessveis fora da unidade ( o arquivo de cdigo-fonte) que declara a classe. A diretiva de public (pblico) denota campos e mtodos que so livremente acessveis a partir de qualquer outra parte de um programa, assim como a unidade em que eles so definidos. A diretiva protected (protegida) usada para indicar mtodos e campos com visibilidade limitada. Apenas a classe atual e suas subclasses podem acessar elementos protected. Vamos discutir essa palavra-chave novamente, mais frente, na seo Campos Protegidos e Encapsulamento.

Em geral, os campos de uma classe devem ser private; os mtodos normalmente so public. Entretanto, nem sempre esse o caso. Os mtodos podem ser private ou protected, se eles forem necessrios apenas internamente, para realizar algum calculo parcial. Os campos podem ser protected ou public, quando voc quiser um acesso fcil e direto e tiver absoluta certeza de que sua definio de tipo no vai mudar. Os especificadores de acesso apenas restringem o cdigo fora de sua unidade do acesso a certos membros de classes declaradas na seo interface de sua unidade. Isso significa que, se duas classes esto na mesma unidade, no h proteo para seus campos private. Apenas colocando-se uma classe na parte referente interface de uma unidade que voc limitar a visibilidade de classes e funes em outras unidades para o mtodo e os campos public da classe. Como exemplo, considere a seguinte nova verso da classe TDate: Type TDate = class Private Month, Day, Year: Integer; Public Procedure SetValue (m,d,y : integer); Function GetText: string; Procedure Increase; End; Nessa verso, os campos so agora declarados como private e existem alguns mtodos novos. O primeiro, GetText, uma funo que retorna uma string com a data. Voc poderia considerar a incluso de outras funes, como GetDay,GetMonth e GetYear, que simplesmente retornam os dados private correspondentes, mas nem sempre as funes de acesso a dados diretas so necessrias. Fornecer funes de acesso para cada e todo campo poderia reduzir o encapsulamento e dificultar modificar a implementao interna de uma classe. As funes de acesso devem ser fornecidas apenas se fizerem parte de interface lgica da classe que voc esta implementando. O segundo mtodo novo o procedimento Increase, que aumenta a data em um dia. Isso esta longe de ser simples, pois voc precisa considerar os diferentes comprimentos dos vrios meses, assim como os anos bissextos e normais. O que faremos para facilitar escrever o cdigo mudar a implementao interna da classe para usar o tipo TDateTime do Delphi para ela. A classe mudar para: Type TDate = class Private fDate : TDateTime; public

procedure SetValue (m, d, y: Integer); function LeapYear: Boolean; function GetText: string; procedure Increase; end; Observe que, como a nica alterao se da na parte private da classe, voc no ter que modificar nenhum de seus programas existentes que a utilizam. Essa a vantagem do encapsulamento!

Encapsulamento e FormulriosUma das idias-chave do encapsulamento reduzir o nmero de variveis globais usadas por um programa. Uma varivel global pode ser acessada a partir de qualquer parte de um programa. Por isso uma mudana em uma varivel global afeta o programa inteiro. Por outro lado, ao mudar a representao de um campo uma classe, voc s precisa mudar o cdigo de alguns mtodos dessa classe e nada mais. Portanto, podemos dizer que o ocultamento de informaes se refere a encapsular alteraes. Vamos esclarecer essa idia com um exemplo. Quando voc tem um programa com vrios formulrios, pode tornar alguns dados disponveis para cada formulrio, declarando-os como uma varivel global na parte referente a interface da unidade de um dos formulrios: Var Form1:TForm1; nClicks: Integer; Isso funciona, mas tem dois problemas. Primeiramente, os dados no esto conectados a uma instncia especfica do formulrio, mas ao programa inteiro. Se voc criar dois formulrios do mesmo tipo, eles compartilharo os dados. Se voc quiser que cada formulrio do mesmo tipo tenha sua prpria cpia dos dados, a nica soluo inclu-lo na classe de formulrio: Type TForm1 = class (TForm) Public nClicks : Integer; end; O Segundo problema que, se voc definir os dados como uma varivel global ou como um campo public de um formulrio, no poder modificar sua implementao no futuro sem afetar o cdigo que utiliza os dados. Por exemplo, se voc precisa

apenas ler o valor atual de outros formulrios, pode declarar os dados como private e fornecer um mtodo para ler o valor: type TForm1 = class (TForm) Public Function GetClicks: Integer; Private nClicks: Integer; end; function TForm1.Getclicks: Integer; begin Result:= nClicks; End; Uma soluo ainda melhor incluir uma propriedade no formulrio, conforme veremos no prximo captulo.

A palavra-chave SelfVimos que os mtodos so muito parecidos com procedimentos e funes. A diferena real que os mtodos tm um parmetro implcito, que uma referncia ao objeto atual. Dentro de um mtodo, voc pode fazer referncia a esse parmetro o objeto atual usando a palavra-chave Self. Esse parmetro oculto extra necessrio quando voc teria vrios objetos da mesma classe, para que sempre que um mtodo for aplicado a um dos objetos, ele opere apenas em seus prprios dados e no afete os outros objetos irmos. Por exemplo, no mtodo SetValue da classe TDate, j listada, usamos simplesmente Month Year e Day para fazer referncia aos campos do objeto atual, que voc poderia expressar como : Self.Month := m; Self.Day:= d; assim que o compilador do Delphi traduz o cdigo e no como voc supostamente vai escrev-lo. A palavra-chave Self uma construo fundamental da linguagem, usada pelo compilador, mas s vezes ela usada pelos programadores para solucionar conflitos de nome e para tornar mais legvel em cdigo complicado (as linguagens C++ e Java tm um recurso semelhante, baseado na palavra-chave this.) O que voc precisa realmente saber sobre a palavra-chave Self que a implementao tcnica de uma chamada para um mtodo difere daquela da chamada de uma subrotina genrica. Os mtodos tm um parmetro oculto extra, Self. Como tudo isso

acontece nos bastidores, voc no precisa saber como a palavra-chave Self funciona neste momento.

Criando Componentes DinamicamenteNo Delphi, a palavra-chave Self usada com freqncia quando voc precisa fazer referncia ao formulrio atual explicitamente em um de seus mtodos. O exemplo tpico a criao de um componente em tempo de execuo, em que voc deve passar o proprietrio do componente para seu construtor Create e atribuir o mesmo valor sua propriedade Parent. (A diferena entre as propriedades Owner e Parent ser discutida no prximo capitulo.) Nos dois casos, voc precisa fornecer o formulrio atual como parmetro ou valor, e a melhor maneira de fazer isso usando a palavra-chave Self. Para demonstrar esse tipo de cdigo, escrevemos o exemplo CreateC ( o nome quer dizer Create Componente cria componente). Esse programa tem um formulrio simples sem nenhum componente e um manipulador para seu evento OnMouseDown. Usamos o evento OnMouseDown porque ele recebe como parmetro a posio do clique de ouse (diferentemente do evento OnClick). Precisamos dessa informao para criar um componente boto nessa posio. Aqui est o cdigo do mtodo: Procedure TForm1.FormMouseDown (Sender:TObject; Button: TMouseButton; Shift : TShiftState; X, Y :Integer); Var Btn:TButton; Begin Btn:= TButton.Create (Self); Btn.Parent:=Self; Btn.Left:=X; Btn.top:=Y; Btn.Width:= Btn.Width + 50; Btn.Caption:= Format (Button at %d, %d,*x,y+); End; O efeito desse cdigo criar botes nas posies do clique do mouse, com um titulo indicando a localizao exata, conforme voc pode ver na Figura 2.1. no cdigo anterior, observe em particular o uso da palavra-chave Self, como o parmetro do mtodo Create e como o valor da propriedade Parent. muito comum escrever cdigo como o mtodo anterior, usando uma instruo with, como na listagem a seguir. Procedure TForm1.FormMouseDwon (Sender:TObject; Button: TMouseButton; Shift:TShiftState; X,Y: Integer); Begin

With TButton.Create (Self) do begin Parent := Self; Left:= X; Top:= y; Width:= Width + 50; Caption:=Format (Button in %d, %d , *x,y+); End; End;

ConstrutoresPara alocar memria a um objeto, chamamos o mtodo Create. Esse um construtor, um mtodo especial que voc pode aplicar a uma classe para alocar memria para uma instncia dessa classe. A instncia retornada pelo construtor e pode ser atribuda a uma varivel para armazenar o objeto e us-lo posteriormente. O construtor TOoject.Create padro inicializa todos os dados da nova instncia como zero. Se voc quiser que os dados de usa instncia comecem com um valor diferente de zero, ento precisar escrever um construtor personalizado para fazer isso. O novo construtor pode se chamar Create ou pode ter qualquer outro nome: basta usar a palavra-chave constructor na frente dele. Observe que, nesse caso, voc no precisa chamar TObject.Create : todo construtor pode alocar automaticamente a memria para uma instncia do objeto simplesmente aplicando esse mtodo especial na classe relacionada. A principal razo para se inserir um construtor personalizado em uma classe para inicializar seus dados. Se voc criar objetos sem inicializ-los, chamar os mtodos posteriormente pode resultar em comportamento estranho ou mesmo em um erro em tempo de execuo. Em vez de esperar que esses erros apaream voc deve usar tcnica preventivas para evit-los logo de cara. Uma tcnica dessas o uso coerente de construtores para inicializar dados dos objetos. Por exemplo, devemos chamar o procedimento SetValue da classe TDate, aps termos criado o objeto. Como alternativa, podemos fornecer um construtor personalizado, que cria o objeto e fornece a ele um valor inicial. Embora, em geral, voc possa usar qualquer nome para um construtor, lembrese de que, se usar um nome que no seja Create, o construtor Create da classe de base TObject ainda estar disponvel. Se voc estiver desenvolvendo e distribuindo cdigo para outras pessoas usarem, um programador que chame esse construtor padro poder sobrepor o cdigo de inicializao que voc forneceu. Definido um construtor Create com alguns parmetros, voc substitui a definio padro por uma nova e torna seu compulsrio. Isso possvel para classes genricas, mas deve ser evitado para

componentes personalizados. Conforme veremos no Capitulo 13, quando voc herda de TComponent, deve anular o construtor Create padro com um parmetro e evitar desativ-lo. Da mesma maneira que uma classe pode ter um construtor personalizado, ela pode ter um destrutor personalizado, um mtodo declarado com a palavra-chave destructor e chamado destroy, que pode realizar alguma limpeza de recursos antes que um objeto seja destrudo. Do mesmo modo como uma chamada de construtor aloca memria para o objeto, uma chamada de destrutor libera a memria. Os destrutores so necessrios apenas para objetos que adquirem recursos em seus construtores ou durante sua existncia. Em vez de chamar Destroy diretamente, um programa deve chamar Free, que chama Destroy apenas se o objeto existir isto , se ele no for nil. Entretanto, lembre-se de que chamar Free no configura o objeto automaticamente como nil; algo que voc mesmo deve fazer! O motivo que o objeto no sabe quais variveis podem estar fazendo referncia a ele,portanto, ele no tem meios de configurar todos como nil.

Mtodos e Construtores SobrecarregadosA partir do Delphi 4, o Object Pascal oferece suporte a funes e mtodos sobrecarregados: voc pode ter vrios mtodos com o mesmo nome, desde que os parmetros sejam diferentes. Verificando os parmetros, o compilador pode determinar qual das verses da rotina voc deseja chamar. Existem duas regras bsicas: Cada verso do mtodo deve ser seguida da palavra-chave overload. As diferenas devem ser no numero ou no tipo dos parmetros, ou em ambos. O tipo de retorno, por outro lado, no pode ser usado para fazer a distino entre dois mtodos. A sobrecarga pode ser aplicada a funes e procedimentos globais e a mtodos de uma classe. Esse recurso particularmente relevante para os construtores, pois podemos ter vrios dele e chamar de Create, o que os torna fceis de serem lembrados. Como exemplo de sobrecarga, inclumos na classe TDate duas verses diferentes do mtodo SetValue: Type TDate = class Public Procedure SetValue (y,m,d: Integer); overload; Procedure SetValue (NewDate: TDateTime); overload; Procedure TDate.SetValue (y, m, d: Integer); Begin fDate:=EncodeDate (y, m,d);

end; procedure TDate.SetValue (NewDate: TDateTime); begin fDate:=NewDate; end; Aps esse passo simples, inclumos na classe dois construtores Create separados, um sem parmetros, o que oculta o construtor padro, e um com os valores de inicializao. O construtor sem parmetros usa como valor padro a data atual: Type TDate = class Public Constructor Create; overload; Constructor Create (y , m ,d :Integer); overload; Constructor TDate.create (y,m,d: Integer); Begin fDate:=EncodeDate (y,m,d :Integer); end; Constructor TDate.Create; begin fDate:=Date; end; Ter esses dois construtores possibilita definer um novo objeto TDate de duas maneiras diferentes: var Day1,Day2:TDate; Begin Day1:=TDate.Create (1999, 12,25); Day2:=TDate.Create ; //hoje

A classe TDate CompletaPor todo este capitulo, mostramos trechos e partes do cdigo-fonte de diferentes verses de uma classe TDate. A verso hoi baseada em trs inteiros para armazenar o ano, o ms e o dia; uma segunda verso usou um campo do tipo TDateTime fornecido

pelo Delphi. Aqui est a parte referente a interface completa da unidade que define a classe TDate: Unit Dates; Interface Type TDate = class Private fDate: TDatetime; function GetYear: Integer; public constructor Create; overload; constructor Create (y, m,d: Integer); overload; procedure SetValue (y,m,d:Integer);overload; procedure SetValue (NewDate: TDateTime); overload; function LeapYear : Boolean; procedure Increase (NumberOfDays: Integer =1); procedure Decrease (NumberOfDays: Integer =1); function GetText : String ; end; o objetivo dos novos mtodos, Increase e Decrease ( que possuem um valor padro para seus parmetros), muito fcil de entender. Se forem chamadas sem parmetros, eles mudam o valor da data para o dia seguinte ou anterior. Se um parmetro NumberOfDays fizer parte da chamada, eles somaro ou subtrairo esse nmero: procedure TDate.Increase (NumberOfDays : Integer = 1); begin fDate := fDate + NumberOfDays; end; GetText retorna uma string com a data formatada, usando a funao DateToStr: Function TDate.GetText : string; Begin GetText := DateToStr (fDate); End; Ja vimos a maioria dos metodos nas seoes anteriores; portanto, nao forneceremos a listagem complete; voce pode encontra-la no codigo do exemplo ViewDate que escrevemos para testar a classe. O formulrio possui um titulo para aprensentar uma

data e seis botes, que podem ser usados para modificar a data. Voc pode ver o formulrio principal do exemplo ViewDate em tempo de execuo, na Figura 2.2. para que o componente rotulo tenha uma aparncia interessante, fizemos com que ele tivesse uma fonte grande, o tornamos da largura do formulrio, configuramos sua propriedade Alignment como taCenter e configuramos sua propriedade AutoSize para False.

O cdigo de inicializao desse programa esta no manipulador evento OnCreate. No mtodo correspondente, criamos uma instncia da classe TDate, inicializamos esse objeto e depois mostramos usa descrio textual na propriedade Caption do rotulo, como se v na Figura 2.2. Procedure TDdateForm.FormCreate ( Sender:TObject); Begin theDay := TDate.Create(1999 , 12 , 25); Label1Date.Caption := TheDay.GetText; End; TheDay um campo private da classe do formulrio, TDateForm. A propsito, o nome da classe escolhido automaticamente pelo Delhi, quando mudamos a propriedade Name do formulrio para DateForm. O objeto ento destrudo, junto com o formulrio: Procedure TDateForm.FormDestroy (Sender: TObject); Begin TheDay.Free; End; Quando o usurio da um clique em um dos seis botoes, precisamos aplicar o metodo correspondente ao objeto TheDay e depois apresentar o novo valor da data no rotulo: Procedure TDateForm.BtnTodayclick (Sender:TObject); Begin TheDay.SetValue (Date) ; LabelDate.Caption := TheDay.GetText; End; Um modo alternativo de escrever o ultimo metodo destruir o objeto atual e criar um novo:

Procedure TDateForm,BtnTodayclick (Sender:TOject); Var NewDay :TDate; Begin TheDay.Free; NewDay := TDate.Create; TheDay := NewDay; LabelDate.Caption:= TheDay.GetText; End; Nessa circunstncia em particular, essa nao uma estratgia muito boa (pois criar um novo objeto e destruir outro ja existente exige muita overhead de tempo, quando precisamos apenas mudar o valor do objeto), mas ela permite lhe mostrar duas tcnicas do Object Pascal. O primeiro detalhe a ser notado que destrumos o objeto anterior, antes de atribuir um novo. A operao de atribuio, na verdade, substitui a referncia, deixando o objeto na memria ( mesmo que nenhum ponteiro esteja fazendo referncia a ele). Quando voc atribui um objeto a outro, o Delphi simplesmente copia a referncia ao objeto presente na memria para o novo objeto/referncia. Se voc quer mesmo mudar os dados presentes dentro de um objeto existente, copie cada campo ou fornea um mtodo especfico para copiar os dados internos. Algumas classes da VCL tm um mtodo Assign, que realiza essa copia profunda. Para ser mais preciso, todas as classes VCL que herdam de TPersistent tem o mtodo Assign, mas a maioria das que herdam de TComponent no a implementa, fazendo aparecer um exceo ao ser chamada.

Herana de tipos ExistentesFreqentemente, precisamos usar uma verso um pouco diferente de uma classe j existente que escrevemos ou que algum fornecer. Por exemplo, voc poderia precisar incluir um novo mtodo ou mudar ligeiramente um mtodo j existente. Voc pode fazer isso com facilidade, modificando o cdigo original, a no ser que queira usar as duas verses diferentes da classe em circunstncias diferentes. Alem disso, se a classe foi originalmente escrita por outra pessoa (incluindo a Borland), talvez voc queira manter suar alteraes em separado. Uma alternativa tpica fazer uma copia da definio do tipo original, mudar seu cdigo para oferecer suporte aos novos recursos e dar um novo nome classe resultante. Isso poderia funcionar, mas tambm poderia criar problemas: na duplicao do cdigo, voc tambm duplica os erros; e se voc quiser incluir um novo recurso, precisar inclu-lo duas ou mais vezes, dependendo do numero de copias do cdigo original que tiver feito. Essa estratgia resulta em dois tipos de dados completamente diferente; portanto, o compilador no pode ajud-lo a tirar proveito das semelhanas entre os dois tipos.

Para resolver esses tipos de problemas ao expressar semelhanas entre classes, o Object Pascal permite que voc defina uma nova classe diretamente a partir de outra j existente. Essa tcnicas conhecida como herana ( ou subclasseamento ou derivao) e um dos elementos fundamentais das linguagem de programao orientada a objetos. Para herdar de uma classe existente, voc precisa apenas indicar essa classe no inicio da declarao da subclasse. Por exemplo, o Delphi faz isso automaticamente, sempre que voc cria um novo formulrio. Type TForm1 = class ( TForm) End; Essa definio simples indica que a classe TForm1 herda todos os mtodos, campos, propriedades e eventos da classe TForm. Voc pode aplicar qualquer mtodo public da classe TForm em um objeto do tipo TForm1. TForm, por sua vez, herda alguns de seus mtodos de outra classe e assim por diante, at a classe TObject. Como um exemplo simples de herana, podemos mudar o programa ViwDate ligeiramente, derivando uma nova classe de TDate e modificando uma de suas funes, GetText. Voc pode encontrar esse cdigo no arquivo DATES.PAS do exemplo ViewD2. Type TNewDate = Class (TDate) Public Function GetText: string; End; Nesse exemplo, TNewDAte derivada de TDate. comum dizer que TDate uma classe ancestral ou progenitora de TNewDate e que TNewDate uma sublclasse, uma classe descendente ou uma classe filha de TDate. Para implementar a nova verso da funo GetText, usamos a funo FormtDateTime, que utiliza ( entre outros recursos) os nomes de ms predefinidos disponveis no Windows; esses nomes dependem das configuraes regionais e de idioma do usurio. Muitas dessas configuraes regionais so, na verdade, copiadas pelo Delphi em constantes definidas na biblioteca, como LongMonthNames, shortMonthNames variables, no arquivo de ajuda do Delphi. Aqui est o mtodo getText, onde dddddd significa o formato de dados longo: Function TNewDate.GetText: string; Begin GetText:= FormatDAteTime (dddddd,fDate); End;

Uma vez definido a nova classe, precisamos usar esse novo tipo de dados no codigo do formulrio do exemplo ViewD2. Basta definir o objeto theDay de tipo TNewDate e chamar seu construtor no mtodo FormCreate: Type TDateForm = class (TForm) ... Private TheDay: TNewDate; //declarao atualizada End; Procedure TDateForm.FormCreate (Sender :TObject); Begin TheDay:=TNewDate.Create(1998, 12,25);//atualizado DateLabel.Caption :=TheDay.GetText; End; Sem quaisquer outras mudanas, o novo exempolo ViewD2 funcionar corretamente. A classe TNewDate herda os metodos para aumentar a data, somar um numero de dias etc. Alm disso, o cdigo antigo que chama esses metodos ainda funciona. Na verdade, para chamar uma nova verso do mtodo GetText, no precisamos mudar o cdigo-fonte! O compilador do Delphi vincular automaticamente essa chamada a um novo mtodo. O cdigo-fonte de todos os outros manipuladores de eventos permanece exatamente o mesmo, embora seu significado mude consideravelmente, conforme a nova sada demonstra

Campos Protegidos e EncapsulamentoO cdigo do mtodo GetText da classe TNewDate ser compilado apenas se for escrito na mesma unidade que a classe TDate. Na verdade, ele acessa o campo private fDate da classe ancestral. Se quisermos colocar a classe descendente em uma nova unidade, devemos declarar o campo fDate como protected (protegido) ou incluir um mtodo simples, possivelmente protegido, na classe ancestral, para ler o valor do campo privado. Muito desenvolvedores acreditam que a primeira soluo sempre a melhor, pois declarar a maioria dos campos com protegidos tornar uma classe mais extensvel ampliao e tornar mais fcil escrever subclasse. Entretanto, isso viola a idia do encapsulamento. Em uma hierarquia de classes grande, mudar a definio de alguns campos protegidos das classes-base se torna to difcil quanto mudar algumas estruturas de dados globais. Se diz classes derivadas esto acessando esses dados, mudar sua definio significa potencialmente modificar o cdigo em cada uma das dez classes.

Em outras palavras, flexibilidade, extenso e encapsulamento freqentemente se tornam objetivos conflitantes. Quando isso acontece, voc tenta favorecer o encapsulamento. Se voc puder fazer isso sem sacrificar a flexibilidade, melhor ainda. Freqentemente, essa soluo intermediria pode ser obtida usando-se um mtodo virtual, um assunto que discutiremos mais frente na seo Ligao Tardia e Polimorfismo. Se voc optar por no usar encapsulamento para obter uma codificao mais rpida das subclasses, ento seu projeto poder no seguir os princpios a objetos. Acessando Dados Protected de Outras Classes Vimos que, no Delphi, os dados private e protected de uma classe so acessveis para quaisquer funes ou metodos que apaream na mesma unidade que a classe. Por exempolo, considere a classe simples a seguir ( parte do exemplo Protection): Type TTest = class Protected ProtectedData : Integer; Public publicData: Integer; function GetValue : string; end; O mtodo GetValue simplesmente retorna uma string com os dois valores inteiros: Function TTest.GetValue : string; Begin Result := Format ( Public: %d, protected: %d, [publicData, ProtectedData]); End; Uma vez que voce coloque essa classe em sua propria unidade, no poder acessar sua parte protegida diretamente a partir de outra unidades. De acordo com isso, se voc escrever o seguinte cdigo: Procedure TForm1.Button1click (Sender: TObject); Var Obj: TTest; Begin Obj:= TTest.Create; Obj.PublicData:=10; Obj.ProtectedData := 20; // no ser compilado ShowMessage (Obj.GetValue);

Obj.free; End; O compilador apresentar uma mensagem de erro, Undeclared identifier: ProtectedData. Nesse ponto, voc poderia pensar que no h meios de acessar os dados protegidos de uma classe definida em uma unidade diferente. ( isso que os manuais do Delphi e a maioria dos livros de Delphi dizem.) Entretanto, h um modo de contornar isso. Considere o que acontece se voc cria uma classe derivada aparentemente intil, como a seguinte: Type TFake = class (TTest); Agora, se voc fizer uma converso de tipo do objeto na nova classe e acessar os dados protegidos atravs dela, o cdigo ficar como segue: Procedure TForm1.Button2Click (Sender: TObject); Var Obj:TTest; Begin Obj.PublicData := 10; TFake (obj).ProtectedData := 20; //Compila! ShowMessage (obj.GetValue); Obj.Free; End; Esse cdigo complicado e funciona corretamente, conforme voce pod ever atraves da execuo do programa Protection. Como possvel que essa estratgia funcione ? Bem, se voc pensar a respeito, a classe TFake herda automaticamente os campos protected da classe-base TTest e, como a classe TFake est na mesma unidade que o cdigo que tenta acessar os dados dos campos herdados, os dados protected so acessveis. Conforme seria esperado, se voc mover a declarao da classe TFake para uma unidade secundaria, o programa no ser mais compilado. Agora que mostramos como se faz isso, precisamos avis-lo de que violar o mecanismo de proteo de classe dessa maneira provavelmente causar erros em seu programa (a partir do acesso a dados que voc realmente no deveria acessar) e isso se ope a boa tcnica de POO. Etretanto, existem ocasies em que usar essa tcnica a melhor soluo, conforme voc ver, examinando o cdigo-fonte da VCL e o cdigo de muitos componentes do Delphi. Dois exemplos simples que vm a mente so o acesso a propriedade Text da classe TControl e as posies Row e Col do controle DBGrid. Essas duas idias so demonstradas, respectivamente, pelos exemplos TextProp e DBGridCol. ( Esses exemplos so bastante avanados; portanto, sugiro que apenas os programadores com

uma boa base de programao Delphi os leiam neste ponto do texto os outros leitores podem voltar posteriormente.) embora o primeiro caso mostre um exemplo razovel de uso de quebra de converso de tipo, o exemplo DBGrid de Row e Col , na verdade, um exemplo de contador que ilustra os riscos do acesso a bits que o escritor da classe opta por no expor. A linha e a coluna de DBGrid no significam a mesma coisa que em DrawGrid ou StringGrid ( as classes-base). Primeiramente, DBGrid um modo de visualizao virtual dos dados. Quando voc rola paa cima em uma DBGrid, os dados podem mudar debaixo dela, mas a linha correntemente selecionada pode no mudar. Essa tcnica freqentemente descrita como um hack e deve ser evitada quando possvel. O problema no acessar dados protegidos de uma classe na mesma unidade, mas declarar uma classe para o propsito exclusivo de acessar dados protegidos de um objeto existente de uma classe diferente! O perigo dessa tcnica esta na converso de tipo em cdigo de um objeto de uma classe para outra diferente.

Herana e Compatibilidade de TipoA linguagem Pascal tem tipagem estrita. Isso significa que voc no pode, por exemplo, atribuir um valor inteiro a uma varivel Booleana, pelo menos no sem uma converso de tipo explicita. A regra que dois valores tm um tipo compatvel somente se eles forem do mesmo tipo de dados ou (para ser mais preciso ) se seus tipos de dados tiverem o mesmo nome e suas definies vierem da mesma unidade. H uma exceo importante a essa regra, no caso de tipos de classe. Se voc declarar uma classe, como TAnimal, e derivar dela uma nova classe, digamos, TDog, poder ento atribuir um objeto de tipo Tdog a uma varivel de tipo TAnimal. Isso porque um dog (cachorro) um animal! Por tanto, embora isso possa surpreend-lo, as duas chamadas de construtor a seguir so validas: Var MyAnimal1, MyAnimal2: TAnimal; Begin MyAnimal := TAnimal.Create; Myanimal := TDog.Create; Como regra geral, voc pode usar um objeto de uma classe descendente sempre que um objeto de uma classe ancestral for esperado. Entretanto, o inverso no valido; voc no pode usar um objeto de uma classe ancestral quando um objeto de uma classe descendente for esperado. Para simplificar a explicao, que esta isso novamente, em termos de cdigo: MyAnimal := MyDog // Isto est certo MyDog := MyAnimal; // Isto um erro !!!

Antes de vermos as implicaes desse importante recurso da linguagem, voc pode experimentar o exemplo Animals1, que define as duas classes simples TAnimal e TDog: Type TAnimal = class Public Constructor Create; Function GetKind : string; Private Kind : string; End; TDog = class (TAnimal) Public Constructor Create; End; Os dois mtodos Create simplesmente configurando o valor de Kind, que retornado pela funo GetKind. O formulrio por esse exemplo, ilustrado n Figura 2.4, possui um campo private de tipo TAnimal. Uma instncia dessa classe criada e inicializada quando o formulrio criado e sempre que um dos botes de radio selecionado. Procedure TFormAnimals.FormCreate (Sender: TObject); Begin MyAnimal:=TAnimal.Create; End; Procedure TFormAnimals.RbthDogClick (Sender :TObject); Begin MyAnimal.Free; Myanimal:= TDog.Create; End;

Finalmente, o boto Kind chama o mtodo GetKind para o animal atual apresenta o resultado todo no rtulo: Procedure TFormAnimals.BtnKindClick (Sender : TObject);

Begin KindLabel.Caption:= MyAnimal.GetKind; End;

Ligao Tardia e PolimorfismoAs funes e procedimentos do Pascal normalmente so baseados em ligao esttica, que tambem chamada de ligao precoce (early binding). Isso significa que uma chamada de mtodo resolvida pelo compilador ou pelo ligador, que substitui a solicitao por uma chamada posio de memria especfica em que a funo ou o procedimento reside. (Ela conhecida como o endereo da funo.) as linguagens de programao orientadas a objeto permitem o uso de outra forma de ligao, conhecida como ligao dinmica ou ligao tardia (late binding). Nesse caso, o endereo real do mtodo a ser chamada determinado em tempo de execuo, com base no tipo da instncia usada para fazer a chamada. A vantagem dessa tcnica conhecida como polimorfismo. Polimorfismo significa que voc pode escrever uma chamada para um mtodo, aplicando-a em uma varialvel, mas o mtodo que o Delphi realmente chama depende do tipo de objeto a que a varivel est relacionada. O Delphi no pode determinar, at o momento da execuo, a classe real do objeto a que a varivel faz referncia, simplesmente devido regra de compatibilidade de tipos discutidos na seao anterior. Por exemplo, suponha que uma classe e sua subclasse ( vamos dizer, TAnimal e TDog) definem um mtodo e que esse mtodo possui ligao tardia. Agora voc pode aplicar esse mtodo em um varivel genrica, como MyAnimal, a qual, em tempo de execuo, pode fazer referncia a um objeto da calsse TAnimal ou a um objeto da calsse TDog. O mtodo real a ser chamado determiado em tempo de execuo, dependendo da classe do objeto atual. O exemplo Animals2 estende o programa Animals1 para demosntrar essa tcnica. Na nova verso, as classes TAnimal e TDog possuem um novo mtodo: Voice, que significa a sada do som emitido pelo animal selecionado, como texto e como som. Esse mtod definido como virtual na calsse TAnimal e , porteriormente, sobreposto quando definimos a classe TDog atravs do uso das palavras-chave virtual e override: type TAnimal = class Public Function Voice : String ; virtual; TDog = class (TAnimal) Public Function Voice : String ; override;

claro que os dois mtodos tambm precisam ser implementados. Aqui esta uma estratgia simples: Uses MMSystem; Function TAnimal.Voice : string; Begin Voice := Voice of the animal ; playSound ( Anim.wav,0,snd_Async); End; Function TDog.Voice : String; Begin Voice := Arf Arf; PlaySound ( dog.wav,0, snd_Async); End; Agora, qual o efeito da chamada MyAnimal.voice ? Depende. Se a varivel MyAnimal se refere atualmente a um objeto da classe TAnimal, ela chamar o mtodo TAnimal.Voice. Se ela se refere atualmente a um objeto da classe TDog, ento chamar o mtodo TDog.voice. Isso acontece apenas porque a funo virtual. A chamada de MyAnimal.Voice funcionar para um objeto que uma instncia de qualquer descendente da classe TAnimal, mesmo as classes que so definidas aps essa chamada de metodo ou fora de seu escopo. O compilador no precisa saber a respeito de todos os descendentes para fazer a chamada compatvel com eles; apenas a classe ancestral necessria. Em outras palavras essa chamada a MyAnimal.Voice compatvel com todas as futuras subclasses TAnimal. Essa a principal razo tecnica pela qual as linguagens de programaao oritentadas a objetos favorecem a reusabilidade. Voce pode escrever codigo que use classes dentro de uma hierarquia sem qualquer conheciemento das classes especificas que fazem parte dessa hierarquia. Em outras palavras, a hierarquia e o programa ainda extensvel, mesmo quando voce escreveu milhares de linhas de codigo utilizando-a. claro que h uma confio as classes ancestrais da hierarquia precisam ser projetadas com muito cuidado. O programa Animals2 demonstra o uso dessas novas classes e possui um formulrio semelhante ao do ememplo anterior. Esse codigo executado atravs de um clique no boto. Procedure TFormAnimals.BtnVerseClick (Sender : TObject); Begin Label1Voice.Caption := MyAnimal.voice; End;

Na Figura 2.5, voce pode ver um exemplo da saida desse programa. Executando o voc ouvir os sons correspondente produzidos pela chamada da API PlaySound.

Sobrepondo, Redefinindo e Reintroduzindo MtodosConforme acabamos de ver, para sobrepor um mtodo de ligao tardia em uma classe descendente, voc precisa usar a palavra-chave override. Observe que isso pode ocorrer apenas se o mtodo tiver sido definido como virtual na classe ancestral. Caso contrario, se ele fosse um metodo esttico, no haveria meios de ativar a ligao tardia, a no ser mudando o codigo da classe ancestral. As regras so simples: um mtodo definido como esttico permanece esttico em cada subclasse, a no ser que voc o oculte com um novo mtodo virtual que tenha o mesmo nome. Um mtodo definido como virtual permanece com ligaao tardia em cada subclasse. No h como mudar isso, devido ao modo como compilador gera cdigo diferente para mtodos de ligao tardia . Para redefinir um mtodo esttico, voc simplesmente inclui um mtodo em uma subclasse, tendo os mesmos parmetros ou parmetros diferentes do original, sem quaisquer outras especificaes. Para sobrepor um mtodo virtual, voce deve especificar os mesmos parmetros e usar a palavra-chave override: Type MyClasse = class Procedure One ; virtual; Procedure Two; { mtodo esttico} End; MySubClass = class (MyClass) Procedure One; override; Procedure Two; End; Normalmente, existem duas maneiras de sobrepor um mtodo. Uma substituir o mtodo da classe ancestral por uma nova verso. A outra incluir mais algum cdigo no mtodo existente. Isso pode ser feito usando-se a palavra-chave inherited para chamar o mesmo mtodo da classe ancestral. Por exemplo, voc pode escrever:

Procedure MySubClass.One; Begin //novo codigo ... //chama o procedimento hedado MyClass.one Inherited One; End; Voce poderia estar se perguntando por que precisa usar a palavra-chave override. Em outras linguagens, quando redefine um mtodo em um subclasse, voc sobrepe automaticamente o original. Entretanto, tem uma palavra-chave especifica permite que o compilador verifique a correspondencia entre os nomes dos mtodos da classe ancestral e da sublclasse ( grafar erroneamente uma funao redefinida um erro comum em outras linguagens de POO), verifique se o mtodo era virtual na classe ancestral etc. Alm disso, se voce definir um mtodo esttico em qualquer classe herdada por uma classe da biblioteca, no haver problema, mesmo que a biblioteca seja atualizada com um novo mtodo virtual, tendo o mesmo nome que o mtodo que voc definiu. Como seu mtodo no marcado pela palavra-chave override, ele ser considerado um mtodo separado e no uma nova verso do que foi incluido na biblioteca ( algo que provavelmente causaria problemas a seu cdigo). O suporte sobrecarga introduzido no Delphi 4 acrescentou mais algumas complexidade nesse cenrio. Uma subclasse pode fornecer uma nova verso de um mtodo, usando a palavra-chave overload. Se o mtodo possui parmetros diferentes da verso da classe-base, ele se torna efetivamente um mtodo sobrecarregado; caso contrrio, ele substituir o mtodo da classe-base. Aqui est um exemplo: Type TMClass = class Procedure One; End; TMSubClass = class (TMClass) Procedure One (S: string); overload; End; Observe que o mtodo nao precisa ser marcado como overload na classe-base. Entretanto, se o mtodo da classe-base virtual, o compilador aprensenta o aviso Method One hides virtual method of base type TMyClass. Para evitar essa

mensagem do compilador e para instu-lo mais precisamente sobre suas inteoes, voce pode usar a nova diretiva reintroduce: Type TMyClass = class Procedure One; virtual; End; TMySubClass = class (TMyClass) Procedure One (S: string); reintroduce; overload; End; Voce pode encontrar esse codigo no exemplo Reintr e experiment-lo mais.

Mtodos Virtuais versus Mtodos DinmicosNo Delphi, existem duas maneiras diferentes de ativar a ligao tardia. Voc pode declarar o mtodo todo como virtual, conforme vimos antes, ou declar-lo com dynamic. A sintaxe dessas duas palavras-chave exatamente a mesma e o resultado de seu uso tambem o mesmo. O que diferente o mecanismo interno usado pelo compilador para implementar a ligao tardia. Os mtodos virtual so baseados em uma tabela de metodos virtuais (VMT virtual method table - , tambem conhecida como vtable). Uma tabela de mtodo virtual um array de endereos de mtodos. Para uma chamada a um mtodo virtual, o compilador gera o cdigo para pular para um endereo armazenado na n-esima entrada de tabela de mtodos virtuais do objeto. As tabelas de mtodos virtuais porssibilitam uma rpida execuo das chamadas a mtodos. Seu principal inconveniente que elas exigem entrada para cada mtodo virtual de cada classe descendente, mesmo que o mtodoo no seja sobreposto na subclasse. s vezes, isso tem o efeito de propagar as entradas da tabela de mtodos virtuais por toda uma hierarquia de classes ( mesmo para mtodos que no so redefinidos). Isso poder exigir muita memria apenas para armazenar o mesmo endereo de mtodo vrias vezes. As chamadas de mtodos dunamic, por outro lado, so feitas usando-se um nmero exclusivo que indica o mtodo. A busca da funo correspondente em geral mais lenta do que a pesquisa de tabela simples de um passo usada para mtodos virtuais. A vantagem que as entradas de mtodo dynamic s se propagam nos descendentes quandos estes sobrepoem o mtodo. Para as hierarquias de objeto grandes ou profundas, usar mtodos dynamic em vez de mtdos virtual pode resultar em economias de memria significativas, com apenas uma perda mnima de velocidade.

Da perspectiva do programador, a diferena entre essas duas estratgias reside apenas em uma respresentaao interna diferente e em uma velocidade ou utilizao de memria ligeiramente diferente. Fora isso, os mtodos virtual e dynamic so iguais.

Manipuladores de MensagensUm mtodo de ligao tardia tambm pode ser usado para manipular uma mensagem do windows, embora a tcnica seja um tanto diferente. Para esse propsito, o Delphi fornece uma outra diretiva, message, para definir mtodos de manipulao de mensagens, os quais devem ser procedimentos com um nico parmetro var. a diretiva message seguida pelo nmero da mensagem do Windows que o mtodo quer manipular. Por exemplo, o cdigo a seguir permite que voc manipule uma mensagem definida pelo usurio, com o valor numrico indicado pela constante wm_User do Windwos: Type TForm1 = class (TForm) ... Procedure WmUser (var Msg : TMessage); Message wm_user; End; O nome do procedimento e o tipo real dos parmetros ficam por sua conta, embora existam vrios tipos de registro predefinidos para as diversas mensagens do Windows. Essa tcnica pode ser extremamente til para programadores de Windows experimentados, que conhecem a respeito de suas mensagens e da funes de API.

Mtodos AbstratosA palavra-chave abstract usada para declarar mtodos que sero definidos apenas em subclasses da classe atual. A diretiva abstract define completamente o mtodo; no se trata de uma declarao antecipada. Se voc tentar fornecer uma definio para o mtodo, o compilador reclamara. No Object Pascal, voc pode criar instncia de classes que possuem mtodos abstract. Entretanto, quando voce tenta fazer isso, o compilador de 32 bits do Delphi apresenta a mensagem de aviso: Constructing instance of containing abstract methods. Se acontecer de voc chamar um mtodo abastract em tempo de execuo, o Delphi lanar uma exceo, conforme demonstrado pelo exemplo Animals3 a seguir: Voc poderia estar se perguntando por que desejaria usar mtodos abstract. O motivo reside no uso de polimorfismo. Se a classe TAnimal possui o mtodoo abstract Voice, toda subclasse pode redefini-la. A vantagem que agora voce poder usar o objeto

genrico MYAnimal para fazer referncia a cada animal definido por uma subclasse e chamar esse mtodo. Se esse mtodo no estivess presente na interface da classe TAnimal, a chamada no teria sido permitida pelo compilador, que realiza verificao de tipo esttica. Usando um objeto genrico MyAnimal, voc pode chamar apenas o mtodo definido por sua prpria classe, TAnimal. Voc no pode chamar mtodos fornecidos por sublclasses, a menos que a classe progenitora tenha pelo menos a declarao desse mtodo na forma de um mtodo abstract e do erro de chamada abstrata. Aqui esto as interfaces das classes desse novo exemplo: Type TAnimal = class Public Constructor Create; Function GetKind: string; Function voice: string; virtual ; abstract; Private Kind: string; End; TDog = class (TAnimal) Public Constructor Create; Function Voice : string; override; Function Eat: string; virtual; End; TCat = class (TAnimal) Public Constructor Create; Function Voice: string; override; Function Eat: string ; virtual; End; A parte mais interessante a definio da classe TAnimal, que inclui um mtodo abstract virtual: Voice. Tambm importante observar que cada classe derivada sobrepe essa definio e adiciona um novo mtodo virtual, Eat. Quais so as implicaes dessas duas estratgias diferentes? Para chamar a funo Voice, podemos simplesmente escrever o mesmo cdigo que a verso anterior do programa: LavelVoice.Caption:= MyAnimal.Voice;

Com podemos chamar o mtodo Eat? No podemos aplic-lo a um objeto da classe TAnimal. A instruo Label.Voice. Caption := MyAnimal.Eat; Gera o erro de compilao Field identifier expected. Para resolver esse problema, voce pode usar informaes de tipo em tempo de execuo (RTTI runtime tupe infomation) para fazer a converso de tipo do objeto TAnimal para um objeto TCat ou TDog; mas sem a converso de tipo correta, o programa lanar uma exceo. Voc ver um exemplo dessa estratgia na prxima seo. A incluso de definies de mtodo na classe TAnimal uma soluo tipica para o problema, e a presena da palavra-chave abstract favorece essa opo.

Informaes de Tipo em Tempo de ExecuoA regra de compatibilidade de tipo do Object Pascal para classes descendentes permite que voce use uma clase descendente onde uma classe ancestral esperada. Conforme mencionamos anteriormente, o inverso no possvel. Agora, suponha que a classe TDog tenha um mtodo Eat, que no est presente na classe TAnimal. Se a varivel MyAnimal faz referncia a um cachorro, deve ser possivel chamar a funo. Mas, se voc tentar e a varivel estiver fazendo referncia a outra classe, o resultado ser um erro. Fazendo uma converso de tipo explicita, poderiamos causar um erro em tempo de execuo desagradvel ( ou, pior, um problema sutil de sobrescrita de memria), pois o compilador no pode determinar se o tipo deo objeto est correto e se os mtodos que estamos chamando existem realemente. Para resolver o problema, podemos usar tcnicas baseadas em informes de tipo em tempo de execuo. Basicamente, como cada objeto conhece seu tipo e sua classe progenitora, podemos solicitar essas informaes com o operador is ou usar alguns dos mtodos da classe TObkect ( discutida no prximo captulo). Os parmetros de operador is so um objeto e um tipo de classe e o valor de retorno um booleano: If MyAnimal is TDog then A expresso is avaliada como True somente se o objeto MyAnimal estiver se referindo atualmente a um objeto da classe TDog ou um tipo descendente de TDog. Isso significa que , se voc testar se um objeto TDog do tipo TAnimal, o teste ter sucesso. Em outras palavras, essa expresso avaliada como True se voc puder atribuir seguramente o objeto (MyAnimal) a uma varivel do tipo de dados (Tdog) Agora que voce sabe com certeza que o animal um cachorro, pode fazer uma converso de tipo segura. Voce pode fazer essa converso direta escrevendo o seguinte cdgio: If MyAnimal is Tdog then

Begin MyDog := TDog (MyAnimal); Text := Mydog.Eat; End; Essa mesma operao pode ser feita diretamente pelo segundo operador RTTI, as , que converte o objeto apneas se a classe solicitada for compativel com a classe atual. Os parametros do operador as so um objeto e um tio de calsses e o resultado um objeto convertido para o novo tipo de classe. Podemos escrever o seguinte trecho de cdigo: MyDog := MyAnimal as TDog; Text := Mydog.Eat; Se quisermos chamar apenas a funo Eat, tambm podemos usar uma anotao ainda mais curta: (Myanimal as TDog ).Eat; O resultado dessa expresso um objeto do tipo de dados da classe Tdog; portanto, voc pode aplic-lo a qualquer mtodo dessa classe. A diferena entre a converso de tipo tradicional e o uso da converso de tio as que a segunda lana uma exeo se o tipo de objeto no for compatvel com o tipo em que voc est fazendo a converso de tipo. A exceo lanada EInvalidCast ( as excees esto descritas no final deste captulo). Para evitar essa exceo, use o operador is e, se ele tiver sucesso, faa uma converso de tipo pura ( na verdade, no h motivos para se usar is e as em squencia, fazendo a verificao de tipo duas vezes): If MyAnimal is TDog then TDog(MyAnimal).Eat; Os dois operadores RTTI so muito teis no Delphi, pois voc frequentemente quer escrever cdigo genrico que possa ser usado com vrios componentes do mesmo tipo ou mesmo de tipos diferentes. Quando um componente passado com parmetro para um mtodo de resposta de evento, um tipo de dados genricos usado (TObject); portanto, voc frequentemnete precisa fazer a usa converso de tio de volta para o tipo de componente original: Procedure TForm1.Button1click(sender:TObject); Begin If Sender is TButton then End; Essa uma tcnica comum no Delphi e vamos us-la em vrios exemplos por todo o livro, no Capitulo 4, discutiremos novamente os operadores is e as, enquanto focalizamos algumas tcnicas RTTI alternativas, baseadoas em mtodos da classe

TObject. Os dois operadores RTTI, is e as, so extremamente poderosos e voc poderia ficar tentado a consider-los como construes de programao padro. Embora eles sejam realmente poderosos, voc provavelmente deve limitar seu uso a casos especiais. Quando voc precisar resolver um problema complexo envolvendo vrias classes, tente usar primeiro o polimorfismo. Apenas em casos especiais, onde o polimorfismo sozinho no pode ser aplicado, que voc deve tentar usar os operadores RTTI para complement-lo. No use RTTI em vez do polimorfismo. Essa considerada uma prtica de programao ruim, e ela resulta em programas mais lentos. Na verdade, os operadores RTTI tem um impacto negativo no desempenho, pois eles precisam percorrer a hierarquia de calsses para ver se a converso de tipo est correta. Conforme j vimos, as chamadas de mtodo virtual exigem apenas uma pesquisa de memria, que muito mais rpida.