Share |

Programação Orientada aos Objectos em Pascal


Introdução

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:

  • Dar a conhecer noções teóricas básicas sobre o paradigma POO;
  • Apresentar o dialecto Object Pascal;
  • Utilizar ferramentas do Free Pascal e do Delphi para criar programas segundo o paradigma POO;
  • Criar uma classe útil para os leitores.

Doravante, a linguagem Object Pascal será designada tão-somente por Pascal por duas razões:

  1. Genericamente, qualquer dialecto derivado do Pascal (leia-se Standard Pascal) ser designado exacta e somente por Pascal;
  2. Para simplificar a leitura do presente artigo.

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}.

Preliminares em POO

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).

Classes, métodos, propriedades e instâncias

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:

  • Propriedades – altura, peso, idade, sexo, cor do cabelo.
  • Métodos – andar, falar, sentar, dormir, cozinhar, trabalhar.


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…

Herança

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:

  • Classe principal (ou Main Class) – classe que tem os métodos e propriedades gerais;
  • Classe derivada (ou Inherited Class) – classe que herda os métodos e propriedades da anterior – deriva da classe principal.


Voltando à classe Ser Humano:

  1. Mamífero é uma classe principal de Ser Humano;
  2. Caucasiano é uma classe derivada de Ser Humano;
  3. Reino Animal é uma classe principal de Mamífero;


Isto significa então que, respectivamente:

  1. Ser Humano é um Mamífero;
  2. Caucasiano é um Ser Humano;
  3. Mamífero é do Reino Animal.


Visibilidade de métodos e propriedades

A classe pode ter métodos e propriedades com visibilidades diferentes:

  • Públicas – podem ser vistos por qualquer outra classe, programa ou unidade;
  • Privadas – só podem ser vistos pela própria classe, isto é, pelos restantes métodos e propriedades, independentemente da sua visibilidade. Só estão ocultos para fora da classe, e não para dentro da classe. São igualmente ocultos para classes derivadas.
  • Protegidas – são visíveis tão-somente pela classe e por qualquer outra que derive desta.


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:

  • O método falar é protegido, já que não é visível a outras classes como Leão, Pinguim ou Urso, mas é visível às classes que lhe têm herança, como por exemplo Etnias e Raças;
  • A propriedade altura é pública, já que qualquer um pode ver e saber quanto mede um ser humano em altura;


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.

O Object Pascal

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:

  • Free Pascal;
  • Delphi.


Passemos então à implementação em código.

Classes

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;


Métodos

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};


Propriedades

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:

  • Input de um dado (com o seu devido registo);
  • Output desse mesmo dado (com a sua devida leitura).


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.

Construtores

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;


Instância

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:

  • O João pesa 75Kg;
  • O André pesa 64Kg;
  • A Joana pesa os seus leves 52Kg;
  • Já a Mariana chega aos 86Kg;


Destrutor

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;


Implementação prática

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:

  • Métodos:
    • Conversor radianos –> graus;
    • Conversor graus –> radianos.
  • Propriedades:
    • Valor do ângulo em graus;
    • Valor do ângulo em radianos.


Para as propriedades, necessitaremos dos devidos e respectivos métodos de escrita:

  • Definir valor em graus;
  • Definir valor em radianos.


E, para a criação de uma instância da classe, necessitaremos de um construtor, construtor esse que terá dois argumentos:

  • Valor do ângulo;
  • Tipo de ângulo (graus ou radianos).


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.

Conclusão

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.

Links úteis

Fontes bibliográficas de apoio

Sobre o autor

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.


A presente versão teve uma breve actualização do autor após a versão publicada na Revista.



You could leave a comment if you were logged in.
revistaprogramar_arquivo/33_edicao/poo_pascal.txt · Esta página foi modificada pela última vez em: 2012/04/13 20:16 por thoga31

Share |
Voltar ao topo
chimeric.de = chi`s home Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0