POO sem O
O título quer dizer “Programação Orientada a Objetos sem Objetos”. É um exercício muito interessante e elucidativo para entendermos os princípios da abstração OO. Como essa abordagem nos aproxima da natureza dos problemas, e de que trabalho nós estamos sendo poupados usando uma linguagem com suporte a objetos e seus conceitos.
O texto pode parecer meio básico a principio para quem já conhece as estruturas citadas. Peço ainda licença aos adeptos de C, que é uma linguagem conhecida pelo extenso uso de ponteiros, mas vou usar aqui exemplos em Pascal por ter uma sintaxe mais didática.
Registros e Classes
Registros (Records em Pascal ou Structs em C) são estruturas de dados compostas de outros dados, a base histórica para entendermos o conceito de Classe, do ponto de vista prático.
Quando escrevemos um registro, estamos modelando um conceito sobre alguma coisa. Inserimos nesse registro algumas características distintas do conceito, na forma de variáveis tipadas.
No exemplo, definimos um registro simples, um modelo de algo que seja capaz de guardar o nome e a data de nascimento de um indivíduo. Não guardamos os dados diretamente no registro, ele não serve para isso. Se fosse assim, teríamos de escrever um registro para todos os indivíduos que quiséssemos guardar. Ao invés disso, usamos variáveis.
As variáveis P1 e P2 representam indivíduos que têm seus próprios nomes e datas de nascimento, segundo o conceito de Pessoa. Com isso, podemos agora guardar dados de duas pessoas. Para um milhão de indivíduos, podemos definir um milhão de variáveis, ou um vetor com um milhão de elementos (ex.: PX).
Variáveis ocupam memória desde o momento da carga do programa. Variáveis estruturadas ocupam o equivalente à soma das suas partes. Ao invés de projetar um vetor de um milhão, talvez seja melhor começar por um elemento e alocar espaço à medida do necessário.
Ponteiros e Referências
Assim como registros estão para classes, os ponteiros estão para referências para as instancias das classes, ou objetos.
Um ponteiro é um tipo de dado que contém um endereço de memória, um número que indica uma posição na memória da máquina. Quando esse endereço coincide com o de algum dado do programa, podemos chamá-lo de referência para o dado. Um dado pode ter inúmeras referências apontando para ele.
Podemos dizer que um ponteiro não possui dados, na verdade ele aponta para algo que os tenha. Um ponteiro
puro ou não-tipado (Pointer ou Void, ex.: P3) não “conhece” a natureza do dado a que ele se refere. Para manipular o dado através do ponteiro, é necessário fazer o casting para um tipo conhecido como um inteiro, uma string ou um registro, ou “tipar” o ponteiro (ex.: P4, P5).
Ponteiros podem referir dados já existentes, mas também é possível usá-los como variáveis de alocação dinâmica (ex.: P5).
Neste ponto podemos fazer algumas analogias ao modelo OO. O New seria a chamada ao construtor da classe Pessoa, P5 a referência para o objeto criado, e o Dispose a chamada ao destruidor do objeto.
Herança
Um candidato a extensão do nosso conceito de Pessoa pode ser o conceito de Funcionário, por exemplo. Para simplificar nossos exemplos, adicionamos apenas o atributo Salário. A implementação com registros usa composição, com o registro Pessoa sendo uma variável de Funcionário (AsPessoa).
Ao criar uma nova instancia de Funcionário, alocamos por tabela uma variável do tipo Pessoa. O endereço de AsPessoa coincide com o da própria instancia de Funcionário, porque AsPessoa é a primeira variável do registro.
No exemplo, podemos fazer P5 apontar para F1 e manipulá-lo como uma Pessoa, pois há realmente uma instancia de Pessoa lá.
Dessa forma, Funcionário é uma especialização de Pessoa, e Pessoa é uma abstração ou super classe de Funcionário. A variável AsPessoa pode ser usada como referência à super classe (inherited em Delphi ou super em Java).
Procedimentos e Métodos
Métodos podem ser simulados em registros através de ponteiros para procedimentos ou funções. Ponteiros deste tipo podem ser invocados com passagem de parâmetros e dado de retorno.
Acrescentamos um ponteiro do tipo PReajustador ao registro Funcionário. Esse ponteiro pode referenciar qualquer procedimento que possua a mesma assinatura. Diferentes implementações da rotina de reajuste representam um mecanismo de Polimorfismo. O usuário do registro Funcionário chama sempre o mesmo procedimento: F.Reajusta(F, 1.10), mas o resultado depende da implementação escolhida. O primeiro parâmetro deve ser sempre a instancia objeto da ação (Self em Delphi, ou this em Java, C++).
O endereço inicial do ponteiro Reajusta é dado pelo Construtor do Funcionário. O construtor é uma função que aloca um nova instancia de Funcionário e inicializa as suas variáveis, inclusive os ponteiros para procedimentos. Com isso, já dá para imaginar como faríamos um Override no método Reajusta. Escrevemos um descendente de Funcionário, do mesmo jeito que fizemos com Pessoa, aí criamos um construtor para ele que, após invocar o construtor antigo, redireciona o ponteiro Reajusta para outro procedimento.
O conceito de Encapsulamento já pode ser encontrado aqui, ao passo que o usuário não influencia diretamente na forma de cálculo, apenas usa os métodos que a instancia oferece.
Questões de Visibilidade são um pouco mais difíceis de engolir. Muitas implementações de POO-O precedem os nomes de variáveis e procedimentos com um ou dois underscores (“_”). Essa simples convenção sinaliza membros protegidos ou privados e inibe usuários educados de mexer ali.
Há outras formas de definir a visibilidade dos membros de forma mais restritiva. Internamente ao pacote, usa-se um registro com todos os campos nomeados normalmente. Externamente, usuários recebem uma versão do mesmo registro com os campos escondidos em outras estruturas, comunmente vetores de bytes de tamanho equivalente. Estes vetores recebem nomes sugestivos, como Filler1, Filler2, Undefined, Reserved etc.
Acredito mesmo que tudo que pode ser feito com objetos, possa também de alguma forma ser escrito usando registros e ponteiros. O problema é que dá um trabalho hercúleo fazer isso e, no final, vamos olhar para o código e enxergar um monstro obtuso escondendo o real sentido do programa. Eliminar esses “monstros” é o mérito indiscutível da abordagem por objetos.
Observando códigos-fonte antigos escritos em C sem OO, constatamos o uso extensivo de técnicas POO-O. Várias centenas de registros com suas variáveis e procedimentos imitando a orientação a objetos podem ser conferidos nos fontes do Interbase 6.
Vale a pena conferir esse trabalho.
Enviado em Paradigmas | Sem comentários »






Diretor de Tecnologia da Fortes Informática e Mestrando em Informática Aplicada pela Universidade de Fortaleza.