Básico sobre OO em Delphi
Autor: Geek on the beach | Publicado: 2012-02-04T17:59:00-02:00

Introdução

Neste post, vamos abordar o básico sobre OO em Delphi, falaremos sobre a construção de classes e suas principais características, isto é necessário para posteriormente entrarmos de fato na OO avançada em Delphi, por isso vou continuar com posts progressivos ensinando passo a passo.

OO em Delphi

Como falamos anteriormente o Delphi possui suporte a orientação a objetos desde de o Delphi 3, sua sintaxe de OO é baseada em Object Pascal, o que é excelente pois é muito intuitivo e com certeza é uma das melhores formas de lidar com OO que existem, inicialmente nos iremos explorar o básico que é necessário saber sobre classes em Delphi e algumas informações extras necessárias para o bom entendimento, afinal aqui nada se faz sem entender 100% o que ocorre.
O Delphi suporta classes, herança múltipla através de Interface (Iremos abordar isto em outro post futuramente) e polimorfismo, ou seja, tudo que foi falado sobre estes itens anteriormente se aplica 100% no mundo Delphiano e claro, em muitas outras linguagens.
Uma coisa que precisamos ter noção é que diferente de linguagens como Java,C# e C++ o Delphi se baseia em pascal, enquanto essas outras três se baseiam na sintaxe da linguagem C, isso não é pior ou melhor, mas existem coisas em Delphi que são mais fáceis e outras mais difíceis, é impossível determinar a melhor linguagem em tudo, se possível irei tentar comentar sobre as outras, mas somente a titulo de comparação para mostrar a diferença, por favor dispenso xiitas de plantão, alias, gostaria de fazer uma ressalva importante de uma vontade minha, se possível mais pra frente irei comentar de algumas coisas sobre o Delphi em relação a outras linguagens, não para criticar A ou B, mas percebi que ao longo do tempo, se juntássemos Delphi e outras linguagens derivadas da sintaxe C provavelmente sanaríamos todos os empecilhos que encontramos em ambas mas, isso é historia para outro tópico.

Encapsulamento

Encapsular é o ato de proteger, assim como a capsula protege o conteúdo que seria o remédio em si, o encapsulamento em OO protege variáveis ou métodos para que estes não sejam acessados por qualquer um, isto é utilizado para garantir a integridade da informação, quando eu encapsulo uma variável e declaro métodos de acesso (gets e sets ou leitura e escrita), eu estou garantindo que qualquer que seja a informação passada ela somente será gravada em minha variável encapsulada se e somente, passar pelos meus métodos de acesso, isso garante que tal informação deva obrigatoriamente se submeter aos testes restritivos de validação que forem determinadas nos métodos de acesso, isso faz com que ao manipular a variável encapsulada eu tenha certeza que ela contenha somente valores validos que condizem com o esperado. Além de ser uma forma de garantia, o encapsulamento é extremamente pratico, pois evita as repetitivas validações de conteúdo muito comum em programação estruturada.

OO em Delphi e sua implementação

Para fins didáticos não se apeguem as nomenclaturas que irei utilizar, eu sigo mais ou menos os padrões de desenvolvimento descritos pela Boorland, em outra oportunidade irei apresenta-lo junto com outros padrões que ajudam a boa organização do código.

A criação de uma classe em Delphi é feita de maneira simples como podemos ver abaixo:



type

&nbsp;&nbsp;&nbsp; TPessoa = class(TObject)

&nbsp;&nbsp;&nbsp; private

&nbsp;&nbsp;&nbsp; protected

&nbsp;&nbsp;&nbsp; public

&nbsp;&nbsp;&nbsp; published

&nbsp;&nbsp;&nbsp; end;
&nbsp;&nbsp;&nbsp; 
Apesar de vazia, já construímos uma classe TPessoa, logo de inicio podemos reparar duas coisas, a primeira é que está classe herda a classe TObject como podemos ver na seguinte instrução:

&nbsp;&nbsp;&nbsp; TPessoa = class(TObject)

Eu coloquei isso explicitamente, porem mesmo se não colocássemos a classe de herança, desta forma:

&nbsp;&nbsp;&nbsp; TPessoa = class

O resultado seria o mesmo, pelo simples fato de todo objeto no Delphi herdar querendo ou não, direta ou indiretamente, da classe TObject, a segunda coisa que podemos reparar é que existem
áreas dentro da classe, são elas respectivamente:

&nbsp;&nbsp;&nbsp; private, protected, public e published&nbsp;&nbsp;&nbsp;&nbsp;

Nem toda classe utiliza todas essas áreas de declaração, na realidade a utilização delas é diretamente ligada a necessidade e finalidade de sua classe, abaixo eu vou explicar o que cada uma significa.

private: Toda declaração nesta área indica que qualquer variável ou método (procedure ou function) é acessível somente pela própria classe.

protected: Assim como a private as declarações desta área são acessíveis somente pela própria classe, porém, com uma ressalva, essas declarações também são acessíveis pelas classe filhas, ou seja, uma classe B que herda uma classe A, também possui acesso as métodos e variáveis declarados na seção protected da classe A.

public: Tudo que é declarado nesta área como o próprio nome diz, é de domínio publico, pode ser utilizado por qualquer um, seja uma classe filha ou outra classe qualquer.

published: Esta área possui uma função similar a da área public, porém, tem algumas diferenças que irei detalhar de forma avançada mais tarde, mas basicamente a ideia do published é que o que for declarado nessa área não é somente public (De acesso geral), mas também é publicado, estranho não? Aparentemente sim, mas na realidade vamos imaginar o seguinte, quando eu digo que algo é de domínio publico isso significa que ele pode ser usufruído por todos, quando eu declaro um método published, eu não só digo que ele pode ser usufruído por todos, eu também atesto e informo em alto e bom tom que ele é de domínio publico (Curioso não? Isso me parece familiar, alias está é a forma que o governo brasileiro trabalha, ele declara as coisas como public e não como published), no geral as declarações no published são utilizadas para serem acessadas via tempo de design, basicamente os componentes do Delphi utilizam as propriedades passiveis de manipulação em tempo de design (Construção) declaradas na área published, mas não se engane existe muito mais que isso nesse tipo de declaração e irei abordar posteriormente mas, isso não vem ao caso nesse tópico, se não, não o chamaríamos de "Básico".

Property

Diferente de algumas outras linguagens o Delphi implementa uma funcionalidade chamada property, ela é uma espécie de forma de acesso indireta aos métodos de leitura e escrita que modificam minhas variáveis encapsuladas, a property tem três finalidades:

&nbsp;&nbsp;&nbsp; 1- Diminuir a quantidade de métodos listados ao se fazer acesso a uma classe em tempo de design
&nbsp;&nbsp;&nbsp; 2- Esconder o nome dos métodos gets e sets
&nbsp;&nbsp;&nbsp; 3- Tornar mais legível a utilização dos atributos das classes&nbsp;&nbsp;


Para se ter uma ideia, iremos efetuar uma conta simples, toda property pode carregar consigo até dois métodos, um get e um set, se nossa classe possui 50 atributos, tais como, nome,data de nascimento etc... seriam em tese (Sim em tese, pois no Delphi é possível utilizar acesso direto a variável encapsulada) 100 métodos de acesso, 50 gets e 50 sets, ao se listar os métodos dessa classe, você terá que visualizar 100 métodos que além de ser um valor relativamente grande pode não representar intuitivamente a variável encapsulada correta, já com a utilização de propertys, 50 atributos são representados por 50 propertys (1:1), que cuidam de realizar implicitamente o acesso aos métodos gest e sets sozinhas através do tipo de acesso que é feito a variável, se uma atribuição é feita o método set é disparado, se é necessário uma requisição do valor do atributo o método get é utilizado, sinceramente não entendo porque existem linguagens que não implementam isso.

As propertys são utilizadas atribuindo-se um método read (Obrigatório) e um método write (Opcional), repare que o método write é opcional pois determinada property pode ser somente para leitura "read-only", outra característica das propertys é que apesar de não ser obrigatório geralmente ela é vinculada uma variável encapsulada através dos métodos read e write.
Aqui vou demonstrar a atribuição de algumas propertys a classe TPessoa.


type

&nbsp;&nbsp;&nbsp; TPessoa = class(TObject)

&nbsp;&nbsp;&nbsp; private

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FNome: string;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FDataNascimento: TDate;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FSexo: Char;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setNome(const AValue: string);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setNascimento(const AValue: TDate);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setSexo(const AValue: Char);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; function getNome: string;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; function getNascimento: TDate;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; function getSexo: Char;

&nbsp;&nbsp;&nbsp; protected

&nbsp;&nbsp;&nbsp; public

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property Nome: string read getNome write setNome;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property DataNascimento: TDate read getNascimento write setNascimento;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property Sexo: Char read getSexo write setSexo;

&nbsp;&nbsp;&nbsp; published

&nbsp;&nbsp;&nbsp; end;
&nbsp;&nbsp; 
Implementamos em nossa classe TPessoa os atributos, Nome, Data de Nascimento e Sexo, agora vou mostrar como fica a implementação dos métodos sets;


procedure setNome(const AValue: string);

begin

&nbsp;&nbsp;&nbsp; FNome := AValue;

end;




procedure setNascimento(const AValue: TDate);

begin

&nbsp;&nbsp;&nbsp; FDataNascimento := AValue;

end;




procedure setSexo(const AValue: Char);

begin

&nbsp;&nbsp;&nbsp; FSexo := AValue;

end;

Repare o seguinte, eu posso adicionar uma validação dos valores antes de atribuí-los a minha variável encapsulada, veja um exemplo com o método setNome.


procedure setNome(const AValue: string);

begin

&nbsp;&nbsp;&nbsp; if Trim(AValue) = EmptyStr then

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; raise Exception.Create('O Nome da pessoa não por ser vazio ""');

&nbsp;&nbsp;&nbsp; FNome := AValue;

end;

Obs: Mais para frente abordaremos o tratamento de exceção e o comando raise que é utilizado para propaga-la.

Isto garante que nenhum nome de minha classe seja vazio, eu poderia adicionar outras validações como um mínimo de caracteres ou a necessidade de se digitar pelo menos um nome e um sobrenome. 

Implementação dos métodos gets



function getNome: string;

begin

&nbsp;&nbsp;&nbsp; Result := FNome;

end;




function getNascimento: TDate;

begin

&nbsp;&nbsp;&nbsp; Result := FDataNascimento;

end;




function getSexo: Char;

begin

&nbsp;&nbsp;&nbsp; Result := FSexo;

end;

Acima nós temos a implementação dos métodos gets, e analisando bem eles você ira reparar que eles são bem simples, pra falar a verdade eles não fazem praticamente nada, mas eles poderiam fazer por exemplo um acesso a um índice de array ou algum calculo antes de retornar o valor da variável, uma coisa importante a se notar é que pelo Delphi utilizar property em casos como esse não é necessário a implementação de métodos gets, pois a própria property pode fazer acesso diretamente a variável encapsulada, isso também pode acontecer com o método set, mas por boa pratica faça isso somente no método get se necessário.

A implementação com a property acessando diretamente a variável encapsulada ficaria assim:


type

&nbsp;&nbsp;&nbsp; TPessoa = class(TObject)

&nbsp;&nbsp;&nbsp; private

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FNome: string;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FDataNascimento: TDate;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FSexo: Char;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setNome(const AValue: string);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setNascimento(const AValue: TDate);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setSexo(const AValue: Char);

&nbsp;&nbsp;&nbsp; protected

&nbsp;&nbsp;&nbsp; public

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property Nome: string read FNome write setNome;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property DataNascimento: TDate read FDataNascimento write setNascimento;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property Sexo: Char read FSexo write setSexo;

&nbsp;&nbsp;&nbsp; published

&nbsp;&nbsp;&nbsp; end;
&nbsp;&nbsp;&nbsp; 
Esse modo dispensa a implementação dos métodos gets, mas sinceramente isso não é problema, porque se você declara a instrução da property inteira por exemplo:

&nbsp;&nbsp;&nbsp; property Nome: string read getNome write setNome;

E depois pressiona ctrl + shift + c, o próprio Delphi se encarrega de implementar os métodos get e set e declarar uma variável na seção private correspondente ao nome e tipo da property declarada (Que maravilha não?). Em resumo property é um ótimo recurso que infelizmente nem todas as linguagens (Java por ex.) implementam.

Constructor e Destructor

Assim como outras linguagens o Delphi implementa os métodos construdores e destrutores de classes, esses métodos diferente de outras linguagens podem ter qualquer nome contanto que sejam declarados com a diretiva constructor ou destructor antes do método, por boa pratica não é recomendável fugir do padrão de nomes Create e Destroy, a não ser que isso seja necessário devido a uma diretiva overload (Falaremos posteriormente) onde os tipos dos parâmetros são iguais mas a funcionalidade do método é diferente. Apesar da nossa classe não implementar nenhum método construtor ou destrutor ela já vem com estes dois herdados da classe TObject, e como TObject é a classe raiz do Delphi qualquer classe implementada terá os mesmos dois métodos herdados, por padrão os métodos construtores de destrutores da TObject tem a única finalidade de alocar e desalocar o objeto da memória.

Vamos a implementação de um método constructor e um destructor em nossa classe.



type

&nbsp;&nbsp;&nbsp; TPessoa = class(TObject)

&nbsp;&nbsp;&nbsp; private

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FNome: string;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FDataNascimento: TDate;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; FSexo: Char;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setNome(const AValue: string);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setNascimento(const AValue: TDate);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; procedure setSexo(const AValue: Char);

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; function getNome: string;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; function getNascimento: TDate;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; function getSexo: Char;

&nbsp;&nbsp;&nbsp; protected

&nbsp;&nbsp;&nbsp; public

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; constructor Create;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; destructor Destroy; override;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property Nome: string read getNome write setNome;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property DataNascimento: TDate read getNascimento write setNascimento;

&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; property Sexo: Char read getSexo write setSexo;

&nbsp;&nbsp;&nbsp; published

&nbsp;&nbsp;&nbsp; end;

Obs: A diretiva override é necessaria em casos de sobre-escrita do método herdado da classe pai, isso acontece pois nossa classe pai, no caso a TObject possui o método Destroy com a diretiva virtual, que implica que este método deve ser implementado (Sobre-escrito) pelas classes que o herdarem, se você não o fizer o Delphi não acusara erro, mas emitira um warning "Method 'Destroy' hides virtual method of base type 'TObject'", na realidade isso ocorre pois ao não declarar override no método você está "escondendo" o método originário da classe TObject, o correto é utilizar override e chamar o método pai de desalocamento de memória com o comando inherited, que nada mais faz do que chamar um método da classe superior, é possível utilizar inherited Create; inherited Destroy, inherited AlgumMetodo;, mas caso o nome do método não for declarado o Delphi subentende que o método a ser chamado é o equivalente da classe pai que está presente no escopo de chamada da classe filha, ou seja, um inherited no método Destroy da classe TPessoa, já chama automaticamente o método Destroy da classe TObject, você pode utilizar as duas formas, mas quando o método da classe pai exigir a passagem de parâmetros é necessário a utilização do inherited seguido do nome do método, por exemplo:


inherited Destroy('Um parâmetro qualquer');

Agora vamos a implementação dos métodos.


constructor Create;

begin

&nbsp;&nbsp;&nbsp; ShowMessage('Estou Sendo Construido');

end;




destructor Destroy;

begin

&nbsp;&nbsp;&nbsp; ShowMessage('Estou sendo Destruído');

&nbsp;&nbsp;&nbsp; inherited; // chama o destroy da classe TObject

end;

Instanciar e utilizar a classe

A criação das instâncias de classe é bem fácil, ela é feita da seguinte forma


var

&nbsp;&nbsp;&nbsp; MeuObjeto: TPessoa; // Variavel objeto da classe TPessoa;

begin

&nbsp;&nbsp;&nbsp; MeuObjeto&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; := TPessoa.Create;

&nbsp;&nbsp;&nbsp; MeuObjeto.Nome&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; := 'Pedro';

&nbsp;&nbsp;&nbsp; MeuObjeto.DataNascimento := StrToDate('20/03/1975');

&nbsp;&nbsp;&nbsp; MeuObjeto.Sexo&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; := 'M';

&nbsp;&nbsp;&nbsp; ShowMessage(MeuObjeto.Nome + ' - ' +&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DateToStr(MeuObjeto.DataNascimento));

&nbsp;&nbsp;&nbsp; MeuObjeto.Free;

end;

Aqui eu nada mais fiz do que declarar uma variável, instanciá-la, atribuir alguns valores, efetuar alguma operação (ShowMessage), e em seguida destruí-la.

Bom pessoal, esse é a primeira parte do básico de OO aplicado em Delphi, eu tentei me restringir a coisas bem básicas e primordiais mas, assuntos como as diretivas inherited, override, virtual, overload e métodos de destruição como .Free, .Destroy, FreeAndNil etc... serão abordados posteriormente pois merecem um tópico a parte, eu só expliquei o básico para ninguém ficar no vácuo.

Até a próxima