segunda-feira, 2 de abril de 2007

OO no Delphi

O Delphi é um ambiente integrado de desenvolvimento, que se baseia na linguagem de programação Pascal. Mais precisamente, uma extensão do Pascal orientada a objetos cujo nome é "Object Pascal". Recentemente, a partir da versão 7, se não me engano, a Borland rebatizou essa linguagem para Delphi Language. Segundo alguns autores, a causa dessa mudança foi mais por uma questão de marketing, visto que a Borland lançaria a linguagem usada do Delphi em dois novos produtos na época: O Kylix e o Net. O primeiro trata-se da versão do Delphi para desenvolvimento RAD no Linux, mantendo a compatibilidade da linguagem (O que teria sido ótimo se tivesse funcionado). O .Net, embora seja um produto da MS, usaria tb o Delphi como linguagem suportada pelo framework.

A Delphi Language faz parte das linguagens que oferecem suporte a programação orientada a objetos. E eu com isso? O que que é isso? Podemos começar explicando que orientação a objetos é paradigma "novo" (para alguns) que se sustenta em quatro pilares. Abstração, Encapsulamento, Herança e Polimorfismo. Em outra ocasião pretendo aprofundar mais nesses conceitos. Porque, no momento nosso foco esta mais na parte prática da abordagem desse assunto.

Portanto, continuando, é fácil perceber na prática o quanto a orientação a objetos está tão enraizada no Delphi. O próprio ambiente visual de desenvolvimento define, no início de cada projeto, um objeto representando uma janela (window form). Que por sua vez, tem como objetivo principal ser repositório e exibidor de outros objetos (botões, caixas de textos, Caixas de seleção, etc). Que juntos, organizadamente, trabalharão para produzir um resultado específico. Esses objetos, cada um deles, são de um tipo de classe disponível na biblioteca de classes do Delphi. Classes nativas ou incluídas.

Uma classe nada mais é do que um tipo de dado definido pelo usuário. Ou seja, não é um tipo primitivo. Observe que quando falamos de tipo de dados não primitivos, somos imediatamente remetidos a idéia de estrutura de dados (Array, matrizes, records, registros, enumerações e etc.). Só que as Classes são um tipo especial nesse contexto, elas transcendem essa idéia porque nelas o conceito de escopo (visibilidade) é levado as últimas conseqüências. A “priore”, a classe deve possui um estado, que é a sua representação, seus dados internos, e algumas operações, que definem seu comportamento. Por sua vez, o comportamento define a funcionalidade da classe.

Essas operações (funcionalidades), que são descritas em código em forma de funções e procedimentos, em última análise, suas ações refletem sobre os dados da própria classe a alteração do seu estado, hermeticamente. Isso é o encapsulamento.

Uma conveniente analogia com a estruturação nos ajuda a trazer mais luz sobre esse tema. Veja que assim como na estruturação, os dados da classe (também amplamente denominados na literatura como atributos) nada mais são do que variáveis (de um determinado tipo, primitivo ou não). Ao passo que as operações, são funções e procedimentos que manipulam essas variáveis. Eles são conhecidos na literatura pelo nome de métodos da classe. Título esse que particularmente considero bastante ineficiente no que tange a representação da idéia de comportamento, funcionalidade de um objeto. Mas, infelizmente, traduziu-se assim, a cultura da nossa profissão absorveu. Quem sofre com isso são os universitários e profissionais migrando de tecnologias legadas, que após o primeiro capítulo de algum livro sobre OO sentem vontade de dar um tiro na cabeça quando se deparam com a singela afirmação: “O objeto é a instância de uma classe .... blabla .. bla ... Ele possui propriedades e métodos.” Hãããããããããããã? Estância de água o que???????? Creeedooo!?!??!?!?!?!?!?

O Objeto é uma variável cujo tipo é uma classe. Melhor assim? Tem mais identidade com o nosso dia-dia? Pois é, também acho.

Se pularmos mais um montão de “blábláblá vlavlabla” de alguns autores, em alguma parte descobriremos que uma variável cujo tipo é uma classe, não é uma variável estática. Ela é uma variável de referência, ou seja, um ponteiro para uma referencia de memória. Por isso, conforme vimos no post sobre ponteiros, no momento em que usarmos o objeto, temos que alocar memória para ele dinamicamente. Isso é assim em todas as outras linguagens de programação OO, como Java, C++, C# e etc. A idéia é a mesma usada para alocarmos memória para ponteiros. Alocamos e desalocamos memória dinamicamente, contudo no Delphi, a sintaxe difere entre ponteiros e objetos. Vejamos inicialmente dois exemplos:
Exemplo 1, alocação dinâmica de memória para ponteiros:


(* Exemplo de código 1 – Alocação de memória para um ponteiro*)
var
Mypointer: ^Byte;
begin
new(MyPointer);
end;


Exemplo 2, alocação dinâmica de memória para objetos:

(* Exemplo de código 2 – Alocação de memória para um objeto*)

Type
TGmInstrumento = Class
SampleSound: TWaveStream;
Playing: Boolean;
procedure Play;
end;
.
.
.

Var
GmViolao: TGmInstrumento; // Declaração da variável objeto

begin
GmViolao := TGmInstrumento.Create;
GmViolao.Play;
GmViolao.Free;
End;


Um tipo de dado pertencente ou universo das classes possui dois tipos especiais de operação: Uma responsável pela alocação da instância, ou seja, alocação do objeto. O Construtor é um método que em Delphi Language é caracterizado única e exclusivamente pela palavra chave “constructor”. O nome do método pouco importa para o compilador. Porém usa-se por padrão a palvra “Create” na assinatura desse método. No Java funciona ao contrário o construtor da classe tem que possuir o mesmo nome da classe.

(* Exemplo de código 3 *)

public class Instrumento {

private String sampleFileName;

private Position curPosition;

public static void Instrumento(String wavFile) {

// Aqui é o construtor
}
public void play() {
}


A outra operação é de desalocação de memória. O Destrutor é método que em Delphi Language é caracterizado única e exclusivamente pela palavra chave “destructor”. Usa-se por padrão a palvra “Destroy” na assinatura desse método. No Java, diferentemente, não existe tal operação (Um mecanismo da JVM, chamado de garbage collection se encarrega se desalocar objetos que não estão sendo usados). Contudo, o método “Destroy” não deve jamais ser usado em código pelo programador.

No Delphi por convenção usamos a letra “T” como prefixo para o nome que define a classe. A letra “T” nos remete a idéia de “tipo”.

Por causa da herança, em todas as linguagens de programação OO, como disse anteriormente, não existe a possibilidade de uma classe ser criada sem a indicação de qual outra classe ela descende (herda, estende). Existe uma classe base para todas as outras, no Delphi TObject ela cumpre esse papel. Portanto, uma classe que não indica, explicitamente na sua declaração, de qual classe ela herda, por default no Delphi, ela herda de TObject. Logo, onde exemplifiquei a classe “TGmInstrumento”, vc pode concluir que ela herda de TObject. Veja, os dois trechos de código abaixo, em termos de herança são exatamente a mesma coisa.


(* Exemplo de código 4 *)
Type
TGmInstrumento = Class
SampleSound: TWaveStream;
Playng: Boolean;
procedure Play;
end;



(* Exemplo de código 5 *)
Type
TGmInstrumento = Class(TObject)
SampleSound: TWaveStream;
Playng: Boolean;
procedure Play;
end;

Mas lá no exemplo de código 2, vc chamou o método Create sem que ele estive declarado na definição da classe, como pode isso? Tanto o construtor, quanto o destrutor estão definidos em TObject. Logo, por hereditariedade TGmInstrumento pode evocá-los.

Quando eu tenho uma variável estática, o compilador reserva um espaço para ela, estaticamente, no momento da linkedição, para ser alocado na memória stack. Por exemplo: Uma variável do tipo integer que vc declarou no seu programa, quando ele for executado, quando ele sobe pra memória, em algum momento vai ser alocado um espaço de 32 bits para aquela variável. A memória stack é fixa, ela não aumenta de tamanho nem diminui, ela é estática. Em contra partida, quando eu tenho uma variável de referência, um ponteiro, não haverá memória previamente reservada para ela. O compilador vai criar uma referencia para ela na memória heap, pq neste momento ele não sabe a quantidade de memória necessária para alocar. Por isso, a pertinência do "construtor". Através dele, o compilador sabera quantidade de memória necessária para instanciar este objeto. Espaço esse que será alocado na memória heap. Essa memória pode crescer ou diminuir, dinamicamente de acordo com a necessidade do seu programa.

O construtor aloca memória para o objeto e inicializa todas as variávei (campos, atributos) deste objeto. Ou seja, atributos da classe do tipo string, todas são vazias. Todos os numéricos são zero. Todos os booleans são false. Vc pode ter certeza que não existe lixo nessas variáveis. Portanto, quando vc trabalha com objetos não precisa se preocupar em inicializar seus campos. A não ser que algum deles necessite de um valor específico.

Nenhum comentário:

Postar um comentário

 
BlogBlogs.Com.Br