Este documento introduz os principais conceitos da orientação a objetos no Delphi, incluindo classes, objetos, herança, encapsulamento, polimorfismo e associação. A primeira parte descreve como começar a programar orientado a objetos no Delphi usando classes e objetos.
Orientação a Objetos no Delphi - Por onde começar (I)
1. Orientação a Objetos no Delphi: Por onde começar? – Parte I
Ryan Bruno C Padilha
ryan.padilha@gmail.com
http://ryanpadilha.com.br
Objetivo deste artigo
Este artigo é o primeiro de uma série de três artigos onde iremos desenvolver uma aplicação
básica de controle de estoque. A primeira parte da série tem como objetivo fornecer uma
introdução ao paradigma orientado a objetos na linguagem Object Pascal, que é a linguagem
base do ambiente Delphi. A orientação a objetos, comumente chamada de OO é
fundamentada em trazer os objetos da vida real para representar uma entidade na aplicação
por meio de um tipo bem definido, ou seja, através de classes. Antes do surgimento da OO,
muitos desenvolvedores utilizavam ou ainda utilizam o paradigma estruturado, do qual com o
passar do tempo se mostrou pouco flexível, com baixa reutilização de código e inadequado a
projetos de software de grande complexidade.
1. Introdução ao Paradigma Orientado a Objeto
O Object Pascal é uma linguagem híbrida, pois além da orientação a objetos possui também
uma parte da antiga linguagem estruturada - Pascal. Por este motivo não somos forçados a
programar cem por cento orientado a objetos no Delphi, como ocorre em outras linguagens de
programação como Java e C# .NET – que oferecem suporte somente a este paradigma em
questão.
Muitas pessoas quando questionadas se programam utilizando o conceito de orientação a
objetos no Delphi, respondem afirmativamente, porém levando a conversa mais adiante fica
claro que a maioria delas programa no nível do designer, que não exige um conhecimento
profundo dos conceitos da OO. É chamado nível do designer a prática de programação que
utiliza os componentes visuais do Delphi (VCL – Visual Component Library), necessitando
apenas conhecer a sintaxe da linguagem. A diferença aqui é sutil, porém os paradigmas
abordados – orientado a objetos e estruturado – são na teoria e prática diferentes.
A orientação a objetos é amplamente utilizada na análise, projeto e implementação de
projetos de software baseado na composição e interação entre diversas unidades de software
conhecidas como objetos. Os objetos nada mais são do que instâncias de uma determinada
classe (modelo), podem se relacionar entre si através de associações (agregação /
composição); trocar mensagens através de chamadas de métodos (operações); ser objetos
especialistas ou mais genéricos através de herança. Resumidamente, as principais
características da OO são: as classes, a herança, o polimorfismo, seguidas por abstração e
encapsulamento.
Para quem está entrando em contato com os conceito da orientação a objetos neste
momento, pode achar tudo muito confuso e difícil, e isto é esperado, porém, na realidade é
bem mais fácil do que se imagina. Com a utilização constante e um maior entendimento do
paradigma OO, será notado que é mais fácil a implementação e principalmente a manutenção
2. nos projetos de software. Iremos elucidar os principais termos e definições gerais da
orientação a objetos.
1.1 Conceitos Essenciais
Classe
Tipo de dado bem definido através de atributos (variáveis) que armazenam estados (valores);
o comportamento das instâncias dessa classe (conjunto de objetos semelhantes) é definido
através dos métodos, na forma de procedimentos ou funções, que representam as operações
que os mesmos podem realizar. Há dois tipos de classes: 1) Concreta: definida como uma
classe que pode ser instanciada diretamente por um objeto; 2) Abstrata: declarada como uma
classe que pode apenas ser estendida, ou seja, servindo como modelo ou classe base
(superclasse) na hierarquia de classes que estiver inserida, não sendo possível a instanciação
direta por um objeto.
Uma classe é declarada através do uso da palavra reservada class, vejamos a estrutura básica
de uma classe:
Type
// classe definida como TMinhaClasse
// toda classe herda de TObject, sendo sua declaração facultativa
TMinhaClasse = class(TObject)
private
{ atributos / métodos privados }
protected
{ atributos / métodos protegidos }
public
{ atributos / métodos públicos }
end; // fim da declaração da classe TMinhaClasse
Para melhor entendimento segue o exemplo de uma classe TCliente que pode ser
implementada no Delphi.
Type
TCliente = class(TObject)
private
function Inserir(): Boolean;
function Update(): Boolean;
protected
_codigo: String;
_nome: String;
_telefone: String;
public
// propriedades – encapsulamento de atributos
property Codigo: String read getCodigo write setCodigo;
property Nome: String read getNome write setNome;
3. property Telefone: String read getNome write setNome;
// métodos – podem ser declarados como functions ou procedures
function Validar(): Boolean;
function Merge(): Boolean;
function Delete(): Boolean;
function getObject(Id: String): Boolean;
procedure getMaxId;
end;
implementation
// implementação dos métodos declarados acima
// código completo omitido para não estender demais o tópico
Objeto
É uma instância de uma classe, ou seja, é uma variável (dinâmica) do tipo definido pela classe.
Possuindo a capacidade de armazenar estados através de seus atributos e realizar operações
através de seus métodos, definindo seu comportamento. O estado de um objeto pode ser
persistido (armazenado) em um banco de dados objeto-relacional.
Os objetos quando instanciados através da invocação do método Create (método construtor),
são automaticamente alocados na memória Heap, local na memória principal que armazena
todos os objetos que serão utilizados na aplicação. Quando um método que utiliza o objeto é
finalizado, o objeto fica passível de ser coletado pelo Garbage Collector, através da chamada
do método Free (método destrutor).
Todos os objetos no Delphi herdam automaticamente da classe TObject, que possui alguns
métodos auxiliares tais como os métodos especiais Create e Free, porém podemos criar nossos
próprios métodos Create e Free, para inicializar e destruir, respectivamente, conforme
necessário. Agora que temos uma classe definida (TCliente), será demonstrado como
instanciar objetos. Uma variável do tipo da classe deve ser declarada, conforme abaixo:
var
Cliente: TCliente;
Begin
Cliente := TCliente.Create; // instancia o objeto cliente na memória
// setando valores aos atributos do objeto ‘Cliente’
Cliente.Codigo := ‘1’;
Cliente.Nome := ‘Revista Active Delphi’;
Cliente.Telefone := ‘(16) 3024-8713’;
// chamando o método Merge da classe TCliente, que irá persistir o estado do objeto
if Cliente.Merge() then
ShowMessage(‘Cliente Cadastrado com Sucesso!’)
Else
4. ShowMessage(‘Erro ao Gravar o Cliente!’);
Cliente.Free; // chamando o método destrutor, objeto ‘Cliente’ desalocado da memória
end;
Herança
É o mecanismo do qual uma subclasse pode estender outra classe (superclasse), herdando os
atributos e métodos desta, definindo um novo tipo de classe a partir de outra previamente
existente, reutilizando código. Define-se assim um relacionamento de pai/filho entre tipos, o
que origina a hierarquia de classes. Há dois tipos de herança: 1) herança de implementação: a
classe derivada (subclasse) somente poderá assumir uma classe base na herança; 2) herança
de interface: uma ou mais interfaces podem ser referenciadas na herança.
Encapsulamento
Consiste na separação de aspectos internos e externos de um objeto, escondendo os detalhes
de implementação de um objeto. Utilizado amplamente para impedir o acesso direto ao
estado de um objeto (seus atributos). Está ligado a implementação, é necessário expor uma
interface simples e funcional para um objeto, que são definidos como públicos. O programador
somente precisa conhecer a interface do objeto para utilizá-lo, podendo assim alterar a
implementação interna de uma classe sem causar problemas as unidades que as usem.
O recomendado é fornecer acesso aos atributos da classe sempre através de métodos
acessores, que podem operar corretamente com os mesmos, nunca permitir acesso direto aos
atributos, pois com isto perdemos o controle sobre o estado do objeto, tornando-o em alguns
casos inconsistente. O controle do que está visível ou não, a interface pública de uma classe é
definida através dos modificadores de visibilidade que veremos mais adiante.
Modificadores de Visibilidade
Definem a visibilidade de métodos e atributos dentro de uma classe. O Object Pascal possui
três especificadores de acesso básico:
Public (+): Métodos e atributos podem ser acessados de fora da classe, a partir de qualquer
outra unidade. Subclasses herdam todas as assinaturas.
Private (-): Métodos e atributos são somente acessados de dentro da classe, invisível para
outras unidades. Os membros de uma superclasse não são herdados pelas suas subclasses.
Protected (#): Oferece um nível intermediário de acesso entre o public e private. Os membros
de uma superclasse podem ser acessados por membros dessa superclasse, por membros de
suas subclasses e por membros de outras classes no mesmo pacote, ou seja, através da
hierarquia de classes.
O mecanismo em Object Pascal para encapsular os atributos em uma classe é definido como
Property (propriedade), sobrecarregando as operações de leitura (read) e escrita (write)
executadas quando a propriedade for acessada. Permite através de métodos acessores que se
trabalhe diretamente com os atributos, realizando chamadas a métodos. Ao se pensar em
5. encapsulamento de atributos, os quais devem sempre estar protegidos, a declaração Property
desempenha papel fundamental ao proteger o objeto, definindo uma forma padronizada de
acesso ao estado do objeto.
Para utilizar Property, os atributos devem ser declarados como private ou protected, os
métodos como private ou protected, e as propriedades como public.
Property NomePropriedade: TipoDado
read [MetodoDeLeitura ou campo] // método get
write [MetodoDeEscrita ou campo; // método set
Na classe TCliente, encapsulamos os atributos do objeto através de Properties, e definimos um
método para efetuar a leitura e escrita em cada propriedade, esses métodos são os famosos
getters e setters, que são métodos auxiliares que operam sobre os atributos.
Polimorfismo
É o meio pelo qual duas ou mais classes derivadas de uma mesma superclasse podem executar
métodos que contem a mesma assinatura (nomenclatura e lista de parâmetros), porém com
comportamento diferentes. Como exemplo temos a superclasse TVeiculo que possui o método
acelerar, uma subclasse TCarro que herda de TVeiculo o método acelerar, e outra subclasse
TAviao que herda também de TVeiculo o método acelerar; porém o método herdado nas duas
subclasses serão re-escritos pois o mecanismo de aceleração de um carro pode ser diferente
de um avião. Concluí-se que apesar das subclasses terem a mesma assinatura, o método
acelerar, possuem implementações diferentes em cada classe, daí o nome polimorfismo –
muitas formas. Encontramos duas formas de implementar o polimorfismo:
1) Sobrecarga: Métodos da mesma classe podem ter o mesmo nome, desde que possuam
quantidade ou tipo de parâmetros diferentes, declarados usando a palavra reservada
overload.
2) Sobrescrita: Métodos da classe derivada podem ter assinaturas idênticas ao da superclasse,
inclusive com parâmetros iguais, declarados usando a palavra reservada override;
Type
TVeiculo = class(TObject) // superclasse (classe base)
protected
procedure acelerar(Value: Integer); virtual;
end;
TCarro = class(TVeiculo) // classe TCarro herda de TVeiculo
protected
procedure acelerar(Value: Integer); override; // re-escrevendo o método acelerar
end;
TAviao = class(TVeiculo) // classe TAviao herda de TVeiculo
protected
6. procedure acelerar(Value: Integer); override; overload;
procedure acelerar(Value: Integer; Altura: Double); overload; // sobrecarga método
end;
Abstração
É definido características essenciais de um objeto que o distingue de outros objetos quaisquer.
Obtemos uma separação do que é essencial para o comportamento do objeto de sua
implementação. Na hierarquia de classes, partimos das classes básicas para as específicas.
Uma classe abstrata serve apenas como base para classes especializadas ou concretas, não
possui implementação e não pode ser instanciada diretamente.
Em Object Pascal pode-se definir um método abstrato em uma superclasse e só implementá-lo
em subclasses, reforçando o polimorfismo. A definição de métodos abstratos é realizado
utilizando a palavra reservada abstract, veja abaixo:
Type
TClasseAbstrata = class
public
function Merge(): Boolean; virtual; abstract;
end;
Associação
É o mecanismo pelo qual um objeto utiliza os recursos de outro. Uma das interações mais ricas
na orientação a objetos é a composição/agregação, a qual nos permite que um objeto
contenha outros objetos criando complexos relacionamentos. Um dos relacionamentos mais
instigantes que a composição/agregação provê é a possibilidade de criar um hierarquia de
objetos, contendo relações todo-parte, de modo que possamos tratar exatamente da mesma
forma objetos individuais, bem como objetos compostos.
uses clCliente; // declarando a unit que contem a class TCliente
Type
TPedido = class(TObject)
private
{ atributos / métodos privados }
protected
_codigo: String;
_cliente: TCliente; // um pedido possui um cliente (*-1), associação
public
constructor Create;
Property Codigo: String read getCodigo write setCodigo;
Property Cliente: TCliente read getCliente write setCliente;
end;
7. Interface
A declaração de uma Interface define um “contrato” a ser seguido para o desenvolvimento de
uma classe. Resumidamente a interface estipula que todas as classes vinculadas a ela deverão
implementar todos os métodos declarados pela interface. Com isso obtemos um determinado
grau de padronização, aumentando a versatilidade do modelo de classes de um modo geral. A
definição de uma interface é realizado utilizando a palavra reservada interface.
Type
ICalculadora = Interface // nenhuma implementação deve ser definida aqui
public
function getResult(): Double;
procedure setResult(Value: Double);
procedure calculate(x, y: Double);
property Result: Double read getResult write setResult;
end;
TCalculadora = class(ICalculadora)
protected
_resultado: Double
public
function getResult(): Double;
procedure setResult(Value: Double);
procedure calculate(x, y: Double);
end;
implementation
// implementação dos métodos definidos na Interface
Notação UML
A orientação a objetos possui uma forma especial de modelagem via diagramas, tal qual como
vemos na área de banco de dados com o DER (Diagrama Entidade-Relacionamento); chamado
de UML (Unified Modeling Language – Linguagem de Modelagem Unificada). Seu objetivo é
permitir que, através de diagramas simples, toda a lógica de interações entre os objetos que
compõe o nosso sistema possa ser representada.
Na UML encontramos diversos tipos de diagramas, um para cada finalidade, porém o mais
importante deles é o diagrama de classe, do qual modelamos e definimos os relacionamentos
entre as classes de uma aplicação. Para a modelagem UML podemos utilizar o software Judy
Community (http://jude.change-vision.com/jude-web/product/community.html).
8. Figura 1 – Modelagem do diagrama de classe simplificado através do Judy.
2. Vantagens e desvantagens da Orientação a Objetos
Orientação a objetos é difícil
O paradigma orientado a objetos introduz conceitos que estão presentes no mundo real,
porém para aqueles que vem da programação estrutural, os conceitos podem não ficar tão
claros de imediato. Aproveitar o que a orientação a objetos pode lhe proporcionar por
completo poderá ser mais trabalhoso inicialmente, porém oferece uma maior organização de
código, que geralmente facilita o trabalho da equipe de desenvolvimento, com uma clara visão
sobre as responsabilidades de cada classe dentro do contexto de uma aplicação; uma maior
reutilização de código através de heranças e associações entre objetos (sem ficar realizando o
famoso ‘CRTL+C’ e ‘CRTL+V’, como de costume no paradigma estrutural – o que está mais
propenso a erros), oferecendo um considerável ganho de produtividade.
A arquitetura de uma aplicação OO se mostra mais organizada a tornando flexível. A
flexibilidade aqui significa realizar alterações futuras de forma fácil. A organização do código-
fonte irá definir a qualidade de uma aplicação, permitindo construir abstrações, que com a
programação estrutural ficaria muito difícil de implementar e estender.
Projetos orientados a objetos são lentos
Os projetos de software orientado a objetos tendem a ser mais complexos, pois envolvem uma
grande gama de fatores, o que pode aparentar uma certa lentidão ao perceber uma série de
heranças e associações entre objetos, quando todos os objetos são instanciados em memória
9. ao efetuar, por exemplo, uma operação como uma visualização de pedido de venda.
Analisando pela lógica, pode ficar evidente que aplicações compostas por várias classes com
foco nas unidades que realizam o processo e não no fluxo de execução (paradigma procedural)
podem vir a ser mais lentos, porém não foi realizado uma experiência real, medindo o tempo
de execução em uma aplicação estruturada e outra orientada a objetos, o que seria
interessante para medir o desempenho de cada uma.
É valido observar que podemos inicializar objetos na memória por demanda, ou seja, instanciá-
los somente quando realmente formos utilizar os mesmos. Esta aparente desvantagem da OO
pode ser recompensada por uma maior facilidade de manutenção, reutilização de código e
flexibilidade do projeto de software. Analisando por outra visão, OO pode ser mais lenta que o
paradigma procedural que por sua vez é mais lento que Assembly. Então, será a linguagem
Assembly a real solução para a aparente lentidão na aplicação de software ? A aparente
lentidão é nosso real problema e de nossos clientes ? Pense nisso!
Modelo Orientado a objetos e bancos de dados relacionais
Um dos problemas que a orientação a objetos encontra é a persistência de objetos em banco
de dados relacionais, ou seja, em tabelas. Neste tipo de bancos de dados não conseguimos
persistir objetos por inteiro, sem que ocorra uma serialização (colocar os atributos e seus
respectivos valores de forma que fiquem em série, de forma seqüencial) do mesmo para que
cada atributo seja mapeado para uma coluna da tabela no banco de dados relacional. O
processo de serialização de objetos quando feito manualmente sem o auxilio de um
framework que possua este propósito é custoso e demorado. Então, qual é a melhor forma e
com um certo grau de produtividade e automatização poderíamos serializar objetos e efetuar
a persistência?
O ambiente Delphi a partir da versão 8, intitulado Delphi 8 for .NET introduziu o framework
.Net que suporta o OR/M NHibernate (http://nhibernate.org) que é uma framework que
realiza o mapeamento de classes de negócio para tabelas relacionais, automatizando assim
tarefas repetitivas de forma elegante, nos poupando da preocupação de como serializar e
recuperar objetos. A vantagem que um OR/M proporciona é a independência de banco de
dados, podendo ser utilizado qualquer banco de dados suportado pelo framework, tornando
as classes de negócios independentes e sem praticamente nenhuma sentença SQL declarada
no código-fonte, pois o OR/M gerencia as instruções SQL necessárias. Com isso obtemos mais
tempo para concentrar esforços no que realmente é importante: as classes de negócio de
nossa aplicação!
Maior reutilização de código
Ao realizar a modelagem de diversas classes, que geralmente interagem entre si através das
associações, construímos aplicações completas para um determinado domínio. A criação de
diversas instâncias de objetos de uma mesma classe proporciona um grande exemplo de
reutilização de código; observamos também que com a mesma modelagem (classe) podemos
obter classes mais especialistas através da herança. Uma maior reutilização de código é obtida
através da criação de componentes de software e da aplicação de Design Patterns.
10. Testes unitários (automação de testes)
Ao desenvolver um software, antes da entrega para o cliente é necessário realizar uma bateria
de testes para verificar a integridade e a qualidade do software do qual estamos oferecendo. O
teste unitário é uma modalidade de teste do qual verifica-se a menor unidade do projeto de
software, que pode ser identificado como um método, uma classe ou até mesmo um objeto.
O teste ocorre de maneira isolada, definindo um conjunto de estímulos (métodos), e dados de
entrada e saída associados a cada estímulo implementado pelo próprio desenvolvedor antes
ou depois da modelagem do método ou classe. No ambiente Delphi encontramos o framework
DUnit (http://dunit.sourceforge.net) que é uma ferramenta open-source para testes de
unidade. O principal motivo para utilizar o DUnit integrado com o Delphi é a economia de
tempo gasto em cada teste, a qualidade dos testes que podemos executar em nossas unidades
de código, promovendo assim um melhor design da aplicação. Ao invés de colocar vários
break-points na unit e ir “debugando manualmente” e verificando o valor de cada variável ou
expressão, podemos escrever diversos estímulos e verificar se o resultado é o esperado.
Figura 2 – Testes unitários sendo executados de forma independente da aplicação principal.
Padrões de Projetos (Design Patterns)
Os padrões de projetos foram propostos em 1994 por Erich Gamma, John Vlissides, Ralph
Johnson e Richard Helm – conhecidos como GoF (Gang of Four). Tem por finalidade descrever
soluções customizadas para problemas recorrentes no desenvolvimento de software orientado
11. a objetos. Com a crescente utilização do paradigma OO, surgiu a necessidade de desenvolver
software de maior qualidade – e com isso diversos padrões foram introduzidos e consolidados,
com o objetivo de aumentar a produtividade e reuso. Os padrões são organizados em: de
criação, estruturais e comportamentais; seu escopo pode abranger uma classe ou objeto.
Com a utilização de padrões de projetos não é necessário “re-pensar” em soluções para o
mesmo problema recorrente, basta utilizar um padrão que já foi amplamente utilizado por
várias empresas de desenvolvimento de software. Além disso definimos um padrão de
comunicação que é compartilhado por toda a equipe de desenvolvimento.
3. Conclusão
O paradigma orientado a objetos é uma evolução do processo de desenvolvimento estrutural e
está conquistando espaço em equipes de desenvolvimento de software, permitindo uma
maior organização de código, tornando os projetos de software flexíveis, ou seja, as alterações
futuras são realizadas de forma fácil, pois temos uma clara visão sobre as responsabilidades de
cada classe e suas interações. É notado um ganho de produtividade considerável através da
herança, permitindo o código ser extensível e reutilizável. Através do diagrama de classes que
a UML oferece temos uma documentação padronizada e simplificada do projeto de software, e
em grandes equipes de desenvolvimento o entendimento de determinada classe pode se
tornar tarefa fácil para quem estiver integrando uma nova equipe. A qualidade de software
pode ser é facilmente alcançado com a utilização da orientação a objetos através dos vários
tópicos mencionados no corpo deste artigo.
Esta primeira parte introduziu os conceitos da orientação a objetos no ambiente Delphi. Nos
próximos artigos iremos abordar a construção de uma aplicação básica de controle de
estoque, colocando em prática todos os conceitos abordados neste artigo.
Caso tenha alguma dúvida entre em contato comigo. Será um prazer ajudar.
Até a segunda parte da série sobre a orientação a objetos.