Há quem pense no Pascal como uma linguagem fraca e antiquada que não acompanhou os novos tempos. Mas, na realidade, o Pascal evoluiu em vários dialectos, sendo o mais proeminente o Delphi.
Hoje em dia é possível programar nesta linguagem segundo o paradigma POO (Programação Orientada aos Objectos), programar DLLs (Dynamic Link Libraries), fazer programas não só em consola mas também com recurso a GUI. Permite igualmente a ligação a bases de dados, como o MySQL, bem como a criação de IDEs. O grande exemplo é o, infelizmente descontinuado, IDE Dev-Pascal, da Bloodshed, um IDE open-source, escrito totalmente em Delphi e recorrendo a uma velha versão do FPC (Free Pascal Compiler) e do GPC (GNU Pascal Compiler) como compiladores opcionais integrados.
Posta esta breve introdução, os objectivos para o presente artigo são:
Doravante, a linguagem Object Pascal será designada tão-somente por Pascal por duas razões:
Os trechos de código aqui presentes foram todos testados recorrendo ao Free Pascal 2.4.4.
Em todos os códigos aqui presentes foi utilizado o modo de compatibilidade com Delphi do Free Pascal, pelo que estará presente nestes a directiva de compilação {$mode delphi}.
O âmbito do presente no artigo não é uma explicação alongada deste paradigma. Contudo, vale uma explicação no âmbito do Pascal e daquilo que será utilizado adiante.
Serão então abordados os conceitos principais deste paradigma, sendo estes: classes, métodos, propriedades, instâncias, herança e visibilidade (ou protecção).
Uma classe é um conjunto de objectos com características iguais. Um método da classe não é mais do que uma capacidade da classe, ou seja, um procedimento ou função. Uma propriedade é um atributo, isto é, uma característica do objecto. Uma instância da classe é um objecto com todas as características dessa mesma classe.
De forma prática, vamos considerar este paradigma num caso da vida real.
Considere-se a classe Ser Humano:
Igor Nunes é uma instância desta classe, por exemplo. Sendo uma instância, Igor Nunes terá exactamente as propriedades altura, peso, idade, etc., bem como os métodos andar, falar, dormir, etc. Já a instância João terá exactamente as mesmas propriedades e métodos. O próprio leitor é uma instância desta classe! Contudo, é de ter em atenção que cada instância tem valores distintos para cada propriedade altura, peso, idade…
Uma das capacidades mais impressionantes e úteis do paradigma POO é a possibilidade de uma classe herdar métodos e propriedades de outra. Neste âmbito, temos então duas classes, uma que deriva de outra:
Voltando à classe Ser Humano:
Isto significa então que, respectivamente:
A classe pode ter métodos e propriedades com visibilidades diferentes:
Mais visibilidades existem, e algumas variam de linguagem para linguagem. Contudo, ficam estas três, as mais importantes.
\\Considerando a classe Ser Humano, temos que, por exemplo:
De referir que, se nenhum bloco de visibilidade for criado, então tudo o que for declarado na classe é considerado público por defeito.
Colocada a teoria essencial do paradigma POO, segue-se a exemplificação da sua implementação em Pascal, não antes de dar a conhecer o dialecto do Pascal que suporta este mesmo paradigma.
Apresentado em 1986, o Object Pascal foi uma linguagem de programação com a mesma sintaxe do Standard Pascal, de 1971, e já com as modificações introduzidas pelo Extended Pascal, e foi concebido por uma equipa da Apple Computer (hoje Apple Inc.), liderada por Larry Tesler, em conjunto com o criador do Pascal, Niklaus Wirth.
O seu nome original teria sido Clascal, e foi concebido devido ao facto de este ser necessário para uma Framework da época, a MacApp.
Deu-se assim o suporte do Pascal ao paradigma POO.
Com a introdução do POO no Pascal, esta linguagem tornou-se muito mais poderosa e moderna, sendo cada vez mais um exemplo da estruturação de um programa, módulo ou biblioteca.
A sintaxe do Pascal variou consoante os dialectos que foram surgindo. Contudo, podemos ter dois padrões de referência, sendo o utilizado neste artigo o segundo:
Passemos então à implementação em código.
Um conjunto novo de palavras reservadas surgiu, e entre elas estão as palavras reservadas Object e Class. É de referir que um Object pode ser qualquer coisa, sendo, portanto, a mãe de todos os tipos de dados, incluindo procedimentos e funções.
Para agora, a palavra que nos interessa é a Class. Com esta palavra reservada criamos uma classe, tal como o seu nome sugere.
Em Pascal, uma classe deverá ser sempre declarada num tipo (type) já que as instâncias da classe serão criadas como variáveis (var).
Este tipo não começa com a palavra reservada begin mas sim com a própria palavra class, mas termina obrigatoriamente com end. Dentro deste bloco que se cria teremos a declaração, mas não programação, dos métodos da classe. Igualmente, aqui estarão as propriedades.
Para definir as visibilidades, podemos criar três blocos. Nenhum deles necessita de estar num bloco begin-end já que se considera que o bloco termina quando é encontrada a declaração de um outro bloco, ou o fim da classe.
Na prática, a estrutura de uma classe será a seguinte:
type Nome_Classe = class private // parte privada protected // parte protegida public // parte pública end;
A parte principal de uma classe é o conjunto dos seus métodos – as “habilidades” da classe, os processos que esta é capaz de realizar. E, em Pascal, os métodos não são mais do que procedimentos e funções.
Na classe, estes apenas são declarados. Nunca são implementados nesta parte:
type Nome_Classe = class private function funcao(const texto : tipo_dado) : tipo_output; public procedure procedimento(const argumentos : tipo_dado); end;
Neste caso não temos métodos protegidos. Como podemos verificar, os métodos da classe são declarados dentro dos seus blocos.
Então, onde são implementados? A resposta é: tal como outro procedimento e função quaisquer: fora do bloco principal do programa.
Neste caso, queremos que a função privada conversor converta um carácter ASCII para o seu sucessor (“A” passará a ser “B”, por exemplo), e escrever será o método público que irá escrever o resultado da função privada conversor.
PROGRAM exemplo; type CEx = class private function conversor(const texto : string) : string; public procedure escrever(const texto : string); end; procedure CEx.escrever(const texto : string); begin writeln(conversor(texto)); end; function CEx.conversor(const texto : string) : string; var i : integer; t : string; begin t := ''; for i:=1 to length(texto) do t := t + succ(texto[i]); result := t; end; BEGIN (* Bloco principal de execução *) END.
Como podemos reparar, apesar de conversor ser uma função privada, escrever pode-lhe aceder já que ambos estes métodos pertencem à mesma classe. Em suma, o programa principal poderá aceder ao procedimento escrever mas nunca à função conversor.
Além disso, e muito importante de reter, é o facto de o método de cada classe ser programado identificando em primeiro lugar qual a classe a que pertence o método, e só depois dizendo o nome do método.
procedure/function classe.método({argumentos}) {: tipo_output};
Uma propriedade, por si só, não possui nenhum valor. Uma propriedade é um “atalho” para uma variável que, e essa sim, possui um valor específico. Por exemplo, o nosso peso é uma medida de massa que aparece na balança, mas, por detrás desse valor, está um mecanismo que mediu, registou e fez output desse mesmo valor. Neste exemplo, abordámos tudo o que há numa propriedade:
Uma propriedade é, então, um atributo da classe que inclui, opcionalmente, um conjunto de dois métodos (um input e outro output) que atribuem e lêem o valor de uma variável da classe. Opcionalmente por uma razão: uma propriedade será ReadOnly (só de leitura) se só fornecer output e WriteOnly (só de escrita) se só poder ser atribuída mas não acedida para leitura.
Considerando o caso geral de uma propriedade que permite leitura e escrita:
property propriedade : tipo_dado read metodo_leitura write metodo_escrita;
Para melhor entender este conceito, vamos partir de um exemplo prático e que será já parte da nossa implementação prática:
type TAngle = class private VDEG : real; // Variável da qual depende a propriedade procedure DefinirValorDEG(const valor : real); public property ValorDEG : real read VDEG write DefinirValorDEG; end; procedure TAngle.DefinirValorDEG(const valor : real); (* Atribui valor à propriedade em graus *) begin self.VDEG := valor; end;
Isto é o que acontece na prática e na grande maioria das propriedades. Como podemos ver, a propriedade, de seu nome ValorDEG, tem uma variável “por detrás” que possui o seu valor: a variável privada VDEG.
Aqui começamos já a ganhar noção da vantagem das visibilidades das propriedades: o que nos interessa é que se veja a propriedade da classe, e não a variável na qual está atribuída o valor dessa propriedade. Assim, mantemos essa variável privada.
Para fazer o output da propriedade, bastará ler o valor da sua variável VDEG, pelo que o nome do método de escrita é o próprio nome da variável. O método de escrita deverá ser sempre uma função, mas como a variável em si é uma função, este processo torna-se válido.
Já a escrita necessita de um procedimento. Neste caso, criámos o procedimento DefinirValorDEG que tem única e exclusivamente um argumento. Todo e qualquer método de escrita deverá ter um e um só argumento, sendo este do mesmo tipo que a variável da qual depende a propriedade. É este argumento que recebe o valor a ser atribuído à propriedade, leia-se à variável.
Neste caso, DefinirValorDEG recebe valor e atribuí-o à variável VDEG. Para não confundir com nenhuma variável global com o mesmo nome que pode ser criado para o programa, utilizamos a palavra reservada self, que indica ao compilador que a variável VDEG é a referente à classe cujo método está ali a ser programado: neste caso, a classe TAngle.
Uma instância de uma classe, que será vista a seguir, não pode surgir do nada. A instância tem de ter um construtor de modo a que a variável que criarmos seja, de facto, uma instância. Caso não façamos a construção, a variável do tipo da classe será uma referência nula e, logo, surgirá uma excepção na execução do programa.
O tipo de dados geral TObject tem o seu construtor: o Create. Vamos invocar este construtor nas nossas classes para podermos construir o nosso. O processo de herança será feito pela palavra reservada inherited.
O construtor deverá ser público, e é declarado pela palavra reservada constructor. Poderá receber argumentos ou não, depende do processo de construção da classe.
Regra geral, com o construtor damos os valores por default às propriedades da classe. Estes valores poderão vir dos argumentos.
type Nome_Classe = class private // parte privada protected // parte protegida public constructor Create({argumentos do construtor}); end; constructor Classe.Create({argumentos do construtor}); begin inherited Create; // Invoca construtor da classe ascendente TObject. (* Código do construtor *) end;
Por fim, agora que a classe está criada, vamos criar instâncias desta. Uma instância é criada por uma variável do tipo da classe. Em Pascal:
var Instância : Nome_Classe;
No bloco de execução, antes de utilizar qualquer método ou atribuir ou ler qualquer propriedade, é necessário criar a instância através do construtor:
Instância := Nome_Classe.Create({argumentos do construtor});
Note-se bem que, no construtor, é o nome da classe que se coloca e não o nome da instância!
E, daqui em diante, pode-se utilizar qualquer método da classe, atribuir e ler propriedades desta, mas, para cada instância, cada valor. Se tivermos dez instâncias, cada uma pode ter um valor diferente para uma mesma propriedade. Isto é perceptível no caso da classe Ser Humano:
Já que neste artigo não vamos criar destrutores, fica a referência que, para qualquer classe, podemos utilizar o destrutor geral: o Free.
Antes de terminar um programa, as instâncias devem ser, então, “destruídas”:
Instância.Free;
Terminada que está a introdução teórica à utilização de Programação Orientada aos Objectos em Pascal, a melhor forma de consolidar estes conhecimentos é com um exercício prático.
Criemos, então, uma classe, de seu nome TAngle, e que permita guardar o valor de um ângulo em graus e radianos e tenha as devidas funções de conversão. Para tal, teremos:
Para as propriedades, necessitaremos dos devidos e respectivos métodos de escrita:
E, para a criação de uma instância da classe, necessitaremos de um construtor, construtor esse que terá dois argumentos:
Para o tipo de ângulo, vamos criar especialmente o tipo de dados TMedidasAngulo, e que não deve ser confundido com a classe TAngle, e que assumirá os “valores” deg ou rad, respectivamente para graus e para radianos.
Além disso, o objectivo é que, quando se cria ou se modifica o ângulo, o valor mude automaticamente para os respectivos valores no outro tipo de ângulo. Ou seja, se atribuirmos em graus, o valor em radianos é modificado automaticamente para o valor correspondente.
Por fim, no programa, vamos testar rapidamente o construtor e os conversores.
Passando tudo isto para Pascal:
{$mode delphi} // Utiliza a nomenclatura do Delphi. PROGRAM Teste_Classes; uses crt, math; // Unidades necessárias type TMedidasAngulo = (Deg, Rad); // Tipo de ângulo: Graus ou Radianos. TAngle = class private (* Só visível para a classe *) // Variáveis onde são guardados os valores das propriedades. VRAD : real; // Radianos VDEG : real; // Graus // Métodos que permitem atribuir valores às propriedades. procedure DefinirValorRAD(const valor : real); procedure DefinirValorDEG(const valor : real); public (* Visível para todo o programa *) // Propriedades property ValorRAD : real read VRAD write DefinirValorRAD; property ValorDEG : real read VDEG write DefinirValorDEG; // Conversores function Deg2Rad(const deg : real) : real; function Rad2Deg(const rad : real) : real; // Construtor da classe constructor Create(const valor : real; TipoAng : TMedidasAngulo); end; constructor TAngle.Create(const valor : real; TipoAng : TMedidasAngulo); (* Construtor *) begin inherited Create; // Invoca construtor da classe ascendente TObject. case TipoAng of // Conversores, consoante o tipo de ângulo introduzido. Rad : begin self.ValorRAD := valor; self.ValorDEG := Rad2Deg(valor); end; Deg : begin self.ValorDEG := valor; self.ValorRAD := Deg2Rad(valor); end; end; end; procedure TAngle.DefinirValorRAD(const valor : real); (* Atribui valor à propriedade em radianos *) begin self.VRAD := valor; self.VDEG := self.Rad2Deg(valor); end; procedure TAngle.DefinirValorDEG(const valor : real); (* Atribui valor à propriedade em graus *) begin self.VDEG := valor; self.VRAD := self.Deg2Rad(valor); end; function TAngle.Deg2Rad(const deg : real) : real; begin result := (pi * deg) / 180; end; function TAngle.Rad2Deg(const rad : real) : real; begin result := (rad * 180) / pi; end; var Angulo : TAngle; BEGIN Angulo := TAngle.Create(90, deg); // cria ângulo de 90º writeln('Em radianos: ', Angulo.Deg2Rad(Angulo.ValorDEG):0:3); // Converte em radianos Angulo.ValorRAD := pi; // atribui 180º em radianos writeln('pi(rad) em graus: ', Angulo.Rad2Deg(Angulo.ValorRAD):0:3); readln; // pausa Angulo.Free(); END.
Apesar de os valores convertidos serem facilmente obtidos pela propriedade correspondente ao tipo de ângulo pretendido, a forma como realizámos a conversão nos procedimentos writeln demonstram como se podem utilizar vários métodos de uma vez só.
Note-se que se pode utilizar o bloco with para se evitar escrever continuamente o nome da instância seguido do método ou propriedade que se pretende. Contudo é necessária precaução no caso de haver várias instâncias e classes cujos métodos e propriedades partilham os mesmos nomes. Em caso de dúvida sobre o que vai o compilador assumir, considere sempre escrever na forma completa: nome_classe.método().
Fica então assim uma classe útil que o leitor poderá utilizar livremente nas suas programações em Pascal. Sinta-se à vontade para a expandir com novos métodos e propriedades, já que a melhor forma de aprender é a tentar.
Após este artigo, percebemos que o Pascal, já há quase 30 anos atrás, suporta o paradigma da Programação Orientada aos Objectos sem que a sua sintaxe original tivesse de ser alterada: apenas houve ligeiras adaptações e a entrada de novas palavras reservadas.
A entrada deste paradigma reforçou ainda mais o objectivo desta linguagem de programação: a programação estruturada, com “cabeça, tronco e membros” como se diz na gíria popular.
Conclui-se, sendo assim, que, apesar de muitos lhe chamarem uma linguagem ultrapassada, o Pascal está longe de ser uma linguagem do passado. Pascal é uma linguagem do século XXI.
Uma lista de documentos úteis da Wiki P@P, relacionados com o presente artigo.
Igor Nunes
Estudante universitário, entrou no mundo da programação aos 14 anos com TI-Basic. Dois anos depois descobriu o Pascal, que ainda hoje é a sua Linguagem de Programação de eleição. Mais recentemente introduziu-se ao VB.NET e ao Delphi.
Membro do P@P desde Abril de 2010 (@thoga31), é actualmente membro da Wiki Team e Moderador Local dos quadros de Pascal e Delphi/Lazarus. Escreveu o novo Tutorial de Pascal da Wiki P@P, bem como os Tutoriais de TI-Basiz Z80 e de Introdução à Lógica e Algoritmia.