Paulo A. Pagliosa
Departamento de Computação e Estatística
Universidade Federal de Mato Grosso do Sul
e-mail: pagliosa@dct.ufms.br
Resumo
O ciclo de desenvolvimento de programas de computador é definido pelas fases de análise,
projeto e implementação. Neste texto apresentaremos um resumo das técnicas de projeto de
sistemas orientado a objetos. Inicialmente, discutiremos os propósitos do projeto de sistemas,
enfatizando as diferenças entre análise e projeto. A seguir, mostraremos os conceitos e
propriedades principais da orientação a objetos. Utilizaremos a UML — Unified Modeling
Language — para descrevermos os modelos de projeto, ilustrados com um estudo de caso.
Finalmente, apresentaremos algumas sugestões de mapeamento dos modelos de projeto para
implementação em Object Pascal, a linguagem de programação orientada a objetos do Delphi,
enfatizando a persistência de objetos usando bases de dados relacionais.
1 Introdução
O desenvolvimento de sistemas computacionais complexos não é uma tarefa trivial. Quando
lidamos com complexidade, natural e mentalmente procuramos isolar do universo complexo
componentes que nos permitem, mais facilmente, visualizar a estrutura e compreender o
comportamento das entidades que, à nossa mente, compõe esse universo. Esses componentes
não são as entidades, mas sim representações simplificadas das entidades. Chamamos essas
representações de modelos.
De acordo com RAMBAUGH et al [4], modelos são úteis porque podem permitir:
• Teste de uma entidade física antes de sua construção. Podemos experimentar o
comportamento de um objeto a partir de modelos físicos (construídos em escala
reduzida) do objeto. É possível, com os recentes avanços da computação,
substituirmos vantajosamente os modelos físicos por modelos computacionais
baseados em física, os quais possibilitam a simulação de muitas estruturas físicas
(veja, por exemplo, PAGLIOSA [3]).
• Comunicação com clientes. Arquitetos e projetistas podem construir modelos para
mostrar seus produtos a seus clientes.
• Visualização. Storyboards de filmes ou de programas de televisão, por exemplo,
possibilitam que seus escritores vejam o fluxo de suas idéias principais, permitindo
eventuais modificações antes do início da escrita do roteiro detalhado.
• Redução de complexidade. Essa é a principal razão da modelagem. Modelamos um
sistema porque, à primeira vista, o sistema é muito complexo para o entendermos
diretamente.
2.1 Classe
Definimos objeto como sendo um conjunto de dados e procedimentos que representam a
estrutura e o comportamento de uma entidade concreta ou abstrata. Chamamos o conjunto de
dados de atributos do objeto e o conjunto de procedimentos de métodos do objeto. Diremos
que objetos com a mesma estrutura e o mesmo comportamento pertencem a uma mesma
classe de objetos. Uma classe, portanto, é uma descrição dos atributos e métodos de
determinado tipo de objeto.
Em Object Pascal, uma classe é um tipo definido pela palavra reservada class, como
exemplificado no Programa 2.1.
1 interface
2 type
3 tPoint = class
4 x: integer;
5 y: integer;
6 constructor Create;
7 constructor Construct(x, y: integer);
8 procedure Copy(p: tPoint);
9 function Equals(p: tPoint): boolean;
10 end;
Programa 2.1 Declaração da classe tPoint.
O que o Programa 2.1 nos diz é que o tipo tPoint é uma classe de objetos que possuem
atributos chamados x e y, do tipo inteiro (linhas 3-4), e métodos chamados Create,
Construct, Copy e Equals (linhas 5-8). Objetos da classe tPoint representam pontos com
coordenadas inteiras x e y tomadas em relação a um sistemas Cartesiano de coordenadas no
plano. Observe que a declaração de uma classe é similar à declaração de um registro, porém
acrescido da declaração dos métodos. De fato, a essência de um objeto pode ser ou a
estruturação da dados ou o processamento. Um objeto, em princípio, pode ser o modelo de
qualquer coisa, desde um processo puro até um dado puro. A implementação dos métodos da
5
classe é mostrada no Programa 2.2. Comentaremos os métodos da classe à medida que formos
abordando novos conceitos.
11 implementation
12 constructor tPoint.Create;
13 begin
14 x := 0;
15 y := 0;
16 end;
17 constructor Construct(x, y: integer);
18 begin
19 self.x := x;
20 self.y := y;
21 end;
22 procedure Copy(p: tPoint);
23 begin
24 x := p.x;
25 y := p.y;
26 end;
27 function Equals(p: tPoint): boolean;
28 begin
29 if x = p.x and y = p.y then
30 Result := true
31 else
32 Result := false;
33 end;
Programa 2.2 Implementação da classe tPoint.
Como podemos observar nas linhas 17-21 do Programa 2.2, o construtor inicializa os atributos
x e y de uma nova instância da classe Point com os valores dos parâmetros x e y
(explicaremos o significado da palavra reservada self na próxima Seção).
estamos enviando a mensagem Copy, a qual toma como parâmetro um objeto p da classe
tPoint — ou, mais propriamente, um ponteiro (implícito) p para um objeto da classe tPoint
—, ao objeto q. Nesse caso, q é o receptor, Copy é o seletor e p define o conjunto de
parâmetros da mensagem. Em resposta à mensagem, o objeto q executa o método Copy,
declarado na classe tPoint (pois q é um objeto da classe tPoint), Programa 2.1, e
implementado nas linhas 22-26 do Programa 2.2.1
Dentro do corpo do método associado a uma mensagem, o objeto receptor da
mensagem é identificado, em Object Pascal, pela palavra reservada self, como demonstrado
nas linhas 19-20 do Programa 2.2. self, portanto, dentro de um método, é um ponteiro
(implícito) para o objeto receptor da mensagem.
Comparemos, agora, as duas sentenças a seguir:
p := tPoint.Construct(100, 100);
q.Copy(p);
1
O método Copy é realmente necessário, pois a expressão q := p simplesmente atribui ao ponteiro q o conteúdo
do ponteiro p, ao invés de atribuir ao conteúdo do objeto apontado por q o conteúdo do objeto apontado por p.
Em orientação a objetos, o primeiro tipo de cópia é chamado de shallow copy, enquanto o último é chamado
deep copy. O método Copy, portanto, implementa deep copy. Da mesma forma, o método Equal, linhas 27-33 do
Programa 2.2, implementa deep equal.
2
Não se trata, realmente, do conceito de método de classe, presente em C++ e Java.
7
45 type
46 tFigure = class
47 LineList: tLine;
48 constructor Create;
49 destructor Destroy;
50 procedure Add(l: tLine);
51 procedure Remove(l: tLine);
52 procedure Draw(c: TCanvas);
53 end;
Programa 2.4 Declaração da classe tFigure.
A classe tFigure representa uma figura definida por uma coleção de linhas. Nesse exemplo,
escolhemos implementar a coleção de linhas de uma figura como uma lista ligada duplamente
encadeada, sendo LineList (linha 47 do Programa 2.4) um ponteiro para o nó inicial da lista.
O método Add (linha 50) adiciona uma linha à figura e o método Remove (linha 51) remove
uma linha da figura. A classe contém, ainda, o construtor Create (linha 48) e o método Draw
(linha 52), responsável pelo desenho da figura.3 O Programa 2.5 mostra a implementação do
método Draw.
A classe tLine representa uma linha de uma figura. A estrutura de um objeto da classe
tLine é definida por dois pontos P1 e P2 (linhas 38-39 do Programa 2.3), por um ponteiro
Figure para o objeto figura do qual a linha faz parte (linha 36) e por dois ponteiros Prev e
Next (linha 37), os quais apontam, respectivamente, para a linha anterior e posterior na lista
de linhas da figura. O comportamento de um objeto da classe é definido pelo método Draw
(linha 52), responsável pelo desenho do objeto. Além disso, a classe contém dois construtores,
chamados Create e Construct (linhas 40-41). A implementação do construtor declarado na
linha 41 é mostrada no Programa 2.6.
3
TCanvas é uma classe da VCL (Visual Component Library) do Delphi.
8
(No Programa 2.1 não declaramos um método chamado Free. Como podemos, então, enviar a
mensagem acima ao objeto p? Responderemos brevemente.) A implementação do destrutor da
classe tLine é mostrada no Programa 2.7.
77 destructor tLine.Destroy;
78 begin
79 P1.Free;
80 P2.Free;
81 if Figure <> nil then
82 Figure.Remove(self);
83 end;
Programa 2.7 Implementação do destrutor tLine.Destroy.
2.5 Encapsulamento
PAGLIOSA [3, página 157] define tipo abstrato de dados como sendo
“o mecanismo através do qual uma linguagem de programação orientada a objetos
fornece suporte à especificação das informações de um objeto e das operações
executadas por um objeto, de forma similar ao tipo de dados de uma linguagem
estruturada. A diferença é que um tipo abstrato de dados esconde as informações
sobre a estrutura do objeto de observadores externos. Com isso, as abstrações de
um sistema são definidas, na prática, por duas partes: uma interface que descreve
quais operações podem ser executadas e uma implementação que determina como
as operações são executadas. A classe é a construção de linguagem mais
comumente utilizada para implementar um tipo abstrato de dados em linguagens de
programação orientadas a objetos.”
De acordo com a definição de tipo abstrato de dados, as variáveis de instância declaradas em
uma definição de classe deveriam ser “escondidas” de um observador externo, sendo
acessíveis somente aos próprios métodos da classe. Afinal, as variáveis de instância definem o
estado interno do objeto. Não é o que ocorre nos exemplos das classes listadas até aqui, nas
quais temos, ambos, atributos e métodos, fazendo parte da interface. Isso significa que para
um para um objeto f da classe tFigure, podemos ter em qualquer método, procedimento ou
função de um programa a sentença
f.LineList := nil;
A atribuição de nil ao atributo LineList de f faz com que a figura perca todas as suas
linhas, as quais continuarão, contudo, a existir na memória do programa.
Em Object Pascal, podemos controlar a visibilidade de um atributo (ou método) de
uma classe com as palavras reservadas public, private e protected. Explicaremos o
significado de public e private com o Programa 2.8, o qual mostra a redefinição da classe
tFigure. (Explicaremos protected posteriormente.)
9
84 type
85 tFigure = class
86 private
87 LineList: tLine;
88 public
89 constructor Create;
90 destructor Destroy;
91 procedure Add(l: tLine);
92 procedure Remove(l: tLine);
93 procedure Draw(c: TCanvas);
94 end;
Programa 2.8 Declaração da nova classe tFigure.
Observe, na linha 88 do Programa 2.8, o uso da palavra reservada public. Significa
que, daquele ponto em diante, todos os métodos (e atributos) declarados na classe são
públicos, ou seja, são diretamente acessíveis, sem restrições, em quaisquer métodos, funções
ou procedimentos do programa. (Quando não usamos public, private ou protected em
uma classe, atributos e métodos são considerados todos públicos, como no Programa 2.4). Na
linha 92, temos a palavra reservada private. Significa que daquele ponto em diante todos os
atributos (e métodos) declarados na classe só podem ser acessados por métodos da própria
classe e (especificamente em Object Pascal) por métodos de outras classes definidas na
mesma unit.
Há duas partes bem distintas na classe no Programa 2.8. A primeira parte começa na
linha 88 e termina na linha 93, e contém os métodos públicos da classe. Chamamos essa parte
de interface, ou protocolo da classe. É na interface que definimos quais mensagens podemos
enviar às instâncias de uma classe, ou seja, quais são as operações que podemos solicitar aos
objetos de uma classe. A segunda parte começa na linha 86 e vai até a linha 87 e corresponde
à definição do estado interno dos objetos da classe.
Via de regra, quando definimos uma classe, declaramos como públicos somente os
métodos da interface da classe. Os métodos que executam operações internas sobre os dados,
se houverem, e os próprios dados, serão “escondidos” na definição de classe. Chamamos essa
propriedade da orientação a objetos de encapsulamento. O objetivo do encapsulamento é
separar o usuário do objeto do programador do objeto. Os benefícios óbvios são a segurança e
o fato de podermos alterar a implementação de um método ou a estrutura de dados
“escondidos” de um objeto sem afetar as aplicações que dele se utilizam. Por exemplo, se
quiséssemos implementar a coleção de linhas de uma figura com um vetor, ao invés de uma
lista ligada duplamente encadeada, os usuários da classe tFigure não sentiriam a mudança,
uma vez que a interface da classe não é modificada.
2.6 Herança
Vamos supor que quiséssemos, além de linhas, ter retângulos em uma figura. Teríamos que
definir uma nova classe chamada, digamos, tRectangle, como mostrado no Programa 2.9
95 type
96 tRectangle = class
97 Figure: tFigure;
98 Next, Prev: tRectangle;
99 P1: tPoint;
100 P2: tPoint;
101 constructor Create(f: tFigure);
102 constructor Construct(f: tFigure; p1, p2: tPoint);
103 destructor Destroy;
104 procedure Draw(c: TCanvas);
105 end;
Programa 2.9 Declaração da classe tRectangle.
10
Teríamos, também, que modificar a classe tFigure, para que possamos adicionar
retângulos em uma figura, desenhar figuras com retângulos e remover retângulos de uma
figura. Uma pequena alteração nos requisitos do sistema pode causar grandes alterações nos
modelos e implementação do sistema. Com a orientação a objetos, nem tanto.
Notemos, inicialmente, as similaridades entre as classes tLine e tRectangle,
declaradas nos Programas 2.3 e 2.9, respectivamente. Podemos reunir as características
comuns das classes tLine e tRectangle em uma outra classe de objetos chamada, por
exemplo, tPrimitive, e derivar tLine e tRectangle de tPrimitive. A nova classe
tPrimitive representa uma generalização de tLine e tRectangle, sendo chamada de
classe base ou superclasse. As classes tLine e tRectangle, por outro lado, são
especializações de tPrimitive, ou seja, além de atributos e métodos específicos, herdam os
atributos e métodos de tPrimitive, a classe base da qual derivam. tLine e tRectangle são
chamadas classes derivadas ou subclasses de tPrimitive. Essa propriedade, em orientação a
objetos, é chamada de herança.
A herança é a principal característica de distinção entre um sistema de programação
orientada a objetos e outros sistemas de programação. Classes são inseridas em uma
hierarquia de especializações de tal forma que uma classe mais especializada herda todas as
características da classe mais geral a qual é subordinada na hierarquia. Em Object Pascal,
todas as classes são derivadas, direta ou indiretamente, da classe TObject. Por isso pudemos
escrever, anteriormente, a sentença
p.Free;
O método Free não é declarado na classe tPoint, mas podemos enviar a mensagem Free
para o objeto p porque tPoint é derivada da classe TObject. O método Free, declarado em
TObject, é herdado pela classe tPoint. O principal benefício proporcionado pelo mecanismo
de herança é a reutilização de código.
Já que construímos uma classe genérica chamada tPrimitive, podemos pensar,
agora, em considerar outras formas geométricas em uma figura, tais como círculos e elipses,
por exemplo. As classes desses objetos seriam, é claro, especializações derivadas da classe
tPrimitive. A nova versão da classe tFigure e a classe tPrimitive são mostradas no
Programa 2.10.
106 type
107 tFigure = class
108 private
109 PrimitiveList: tPrimitive;
110 public
111 constructor Create;
112 destructor Destroy;
113 procedure Add(p: tPrimitive);
114 procedure Remove(p: tPrimitive);
115 procedure Draw(c: TCanvas);
116 end;
117 tPrimitive = class
118 protected
119 Figure: tFigure;
120 private
121 Next: tPrimitive;
122 Prev: tPrimitive;
123 public
124 constructor Create(f: tFigure);
125 virtual destructor Destroy;
126 virtual procedure Draw(c: TCanvas); abstract;
127 end;
Programa 2.10 Declaração das novas classes tFigure e tPrimitive.
11
128 type
129 tLine = class(tPrimitive)
130 private
131 P1: tPoint;
132 P2: tPoint;
133 public
134 constructor Create(f: tFigure);
135 constructor Construct(f: tFigure; p1, p2: tPoint);
136 destructor Destroy; override;
137 procedure Draw(c: TCanvas); override;
138 end;
Programa 2.11 Declaração da nova classe tLine.
Na linha 129 do Programa 2.11, estamos declarando que a classe tLine é derivada da classe
tPrimitive. Nas linhas 136-137, sobrecarregamos os métodos virtuais Destroy e Draw,
declarados como virtuais na classe tPrimitive, linhas 125-126 do Programa 2.10. Somente
com a compreensão do mecanismo de sobrecarga, juntamente com o mecanismo de herança, é
que teremos oportunidade de explorar totalmente os benefícios da orientação a objetos no
desenvolvimento de sistemas de computador (abordaremos sobrecarga na próxima Seção).
A implementação do construtor Create da nova versão da classe tLine é mostrada no
Programa 2.12. A palavra reservada inherited, usada na sentença da linha 141, executa o
método construtor Create da classe base tPrimitive, declarado na linha 124 do Programa
2.10.
2.7 Polimorfismo
O polimorfismo é conhecido em programação como sendo a propriedade de um procedimento
ou função executar com um número e tipo variado de parâmetros, ou de um símbolo
representar operações distintas, de acordo como os tipos de operandos. Em Pascal, por
12
exemplo, o símbolo ‘+’ pode denotar soma de inteiros, soma de reais, união de conjuntos ou
concatenação de cadeias de caracteres. Essa facilidade, conhecida como sobrecarga,
caracteriza o que se costuma chamar acoplamento estático (ou junção anterior), só possível
nos casos onde os tipos de operandos ou parâmetros são conhecidos em tempo de compilação.
Consideremos, como exemplo, o Programa 2.13. Nas linhas 149-150 criamos uma
nova figura e uma nova linha, respectivamente. Na linha 151 enviamos a mensagem Draw à
linha da figura. Como uma linha deriva de um primitivo e, em ambas as classes, tPrimitive
e tLine, temos a declaração do método, podemos perguntar: qual método Draw é acoplado à
mensagem da linha 151, o da classe tPrimitive ou o da classe tLine? A resposta é: o
método Draw da classe tLine, porque p é uma variável da classe tLine.
145 var
146 f: tFigure;
147 p tLine;
148 begin
149 f := tFigure.Create;
150 p := tLine.Construct(f, 100, 100);
151 p.Draw;
152 end;
Programa 2.13 Exemplo de acoplamento estático.
153 var
154 f: tFigure;
155 p: tPrimitive;
156 begin
157 f := tFigure.Create;
158 p := tLine.Construct(f, 100, 100);
159 p.Draw;
160 end;
Programa 2.14 Exemplo de acoplamento dinâmico.
De fato, é o que aconteceria, se o método Draw não fosse declarado, na linha 126 do
Programa 2.10, como sendo virtual. Como Draw é virtual, o método executado na linha 159
do Programa 2.14 é o método Draw da classe tLine, pois a variável p (apesar de ser do tipo
tPrimitive) aponta para um objeto do tipo tLine (derivado de tPrimitive). O método
tLine.Draw é sobrecarregado na linha 137 do Programa 2.11, através do uso da palavra
reservada override.
13
De modo geral, se obj for uma variável de classe X qualquer, e M for um método da
classe X, na sentença
obj.M;
podemos ter duas situações:
• Se o método M não for declarado como virtual na classe X, então o método M da
classe X é executado, independentemente do tipo de objeto cujo endereço é
atribuído à variável obj. O compilador decide qual método acoplar à mensagem
obj.M em tempo de compilação, ou seja, acoplamento estático.
• Se o método M for declarado como virtual na classe X, então o método M a ser
executado é o método M da classe do objeto cujo endereço é atribuído à variável
obj. Essa classe pode ser a própria classe X ou qualquer classe derivada direta ou
indiretamente da classe X. Para uma classe derivada de X, o método M pode ser
herdado de X ou sobrecarregado (override) na classe derivada. O compilador não
sabe qual método acoplar à mensagem obj.M porque, durante a compilação, o
conteúdo da variável obj não é conhecido (uma vez que podemos atribuir à
variável obj o endereço de um objeto de qualquer classe derivada da classe X).
Nesse caso, o acoplamento mensagem/método é efetuado em tempo de execução,
ou seja, acoplamento dinâmico.
Note, finalmente, que o método Draw da classe tPrimitive, linha 126 do Programa
2.10, é declarado como abstract (somente métodos virtuais podem ser abstratos4). Um
método abstrato é um método virtual que não possui implementação. Quando uma classe
possui pelo menos um método abstrato, a classe também é dita ser abstrata. Uma classe
abstrata não pode ter instâncias. A classe tPrimitive é uma classe abstrata. A declaração do
método abstrato Draw na classe tPrimitive define um comportamento genérico para todo
primitivo gráfico de uma figura: sua capacidade de se desenhar. Porém, cada tipo particular de
primitivo deve fornecer sua própria versão do método, pois, afinal de contas, linhas e
retângulos são desenhados de formas distintas. De fato, o método Draw é sobrecarregado nas
classes tLine (Programa 2.11) e tRectangle, derivadas de tPrimitive (caso contrário,
tLine e tRectangle também seriam classes abstratas).
Faz sentido a classe tPrimitive ser abstrata. Não existem instâncias de tPrimitive,
mas sim instâncias de tLine e tRectangle. A classe tPrimitive somente define a estrutura
e o comportamento comuns de um primitivo genérico.
Herança e acoplamento dinâmico são os maiores responsáveis pela “mágica” da
orientação a objetos: a capacidade de código velho funcionar, sem alterações, com código
novo. O Programa 2.15 mostra a implementação do método Draw da classe tFigure. Observe
atentamente a implementação. O programa funcionará corretamente para quaisquer novas
classes de primitivos que inventarmos, desde que essas classe derivem de tPrimitive e
sobrecarreguem o método abstrato Draw.
161 procedure tFigure.Draw(c: TCanvas);
162 var
163 p: tPrimitive;
164 begin
165 p := PrimitiveList;
166 while p <> nil do
167 begin
168 p.Draw(c);
169 p := p.Next;
170 end;
171 end;
Programa 2.15 Implementação do novo método tFigure.Draw.
4
Em Object Pascal, métodos dinâmicos também podem ser abstratos.