Anda di halaman 1dari 13

Projeto de Sistemas Orientado a Objetos

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.

MODELOS Modelos são representações das características principais de uma entidade


concreta ou abstrata, construídas com o propósito de permitir a visualização e a
compreensão da estrutura e do comportamento da entidade.

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.

Abstração Modelos são resultantes de nossa capacidade da abstração. Abstração é o exame


seletivo de certos aspectos de um problema. O objetivo da abstração é isolar os aspectos mais
importantes para a solução do problema e desconsiderar os menos importantes. A abstração
deve sempre ter um propósito, pois é o propósito que permite determinar o que é e o que não é
2

importante. Durante a construção de um modelo, portanto, não procuramos a verdade absoluta


— não há um único modelo “correto” para um problema, pois todas as abstrações são
incompletas e imprecisas —, mas sim se o modelo é adequado a determinado propósito ou
não.
A construção de um modelo é um trabalho altamente criativo. Não há uma solução
final, uma resposta correta que deva ser verificada ao final do trabalho. Os construtores de
modelos, através de um trabalho interativo, procuram garantir que seus modelos satisfaçam os
objetivos e requisitos do projeto para o qual estão sendo construídos. Um modelo, porém, não
é inalterável; é natural que, ao longo do processo de desenvolvimento do projeto, os modelos
sejam modificados e atualizados para refletir novos conhecimentos e experiências de seus
construtores.

1.1 Modelagem e Projeto de Sistemas Orientados a Objetos


Modelos são usualmente empregados em Ciências e Engenharia. Durante o projeto de uma
estrutura, por exemplo, o engenheiro elabora um modelo da estrutura através do qual é
possível visualizar e predizer o comportamento do objeto real, antes de sua construção. O
desenvolvimento de hardware e software não é exceção. Ao construir um sistema complexo, o
desenvolvedor deve abstrair diferentes visões do sistema, elaborar modelos através de uma
notação precisa, verificar se esses modelos satisfazem os requisitos do sistema e gradualmente
adicionar detalhes para transformar os modelos em implementação
Neste texto, trataremos sobre modelagem e projeto de sistemas orientados a objetos. A
orientação a objetos é a tecnologia de desenvolvimento e modelagem de sistemas que facilita
a construção de programas complexos a partir de componentes individuais, através de
ferramentas e conceitos que nos permitem modelar o mundo real tão próximo quanto possível
da visão que temos desse mundo. Conforme citado em PAGLIOSA [3, página 153],
“(...) a programação orientada a objetos permite uma representação mais direta do
mundo real no código. Como resultado, a transformação radical normal dos
requisitos do sistema (definidos em termos de do usuário) em especificações do
sistema (definidas em termos computacionais) é amplamente reduzida.”
A orientação a objetos permite que a modelagem do problema em torno de conceitos do
mundo real seja estendida para as fases de projeto e implementação do sistema, encorajando a
reutilização de código e a clareza de programação, e permitindo a criação de sistemas que são
mais facilmente compreendidos e compartilhados com outras pessoas.

1.2 Metodologia de Desenvolvimento Orientado a Objetos


Há uma variedade de métodos de desenvolvimento de sistemas orientados a objetos (veja, por
exemplo, ERIKSSON [1, página 3]), cuja discussão está além do escopo deste texto. Para
nossos propósitos, vamos considerar o processo tradicional de desenvolvimento de software, o
qual consiste das seguintes fases:
• Análise. A partir dos requisitos do sistema, ou da sentença do problema, o analista
constrói um modelo que mostra as propriedades mais importantes da situação do
mundo real. O analista deve trabalhar com o cliente para compreender o problema
porque a sentença do problema raramente é completa ou correta. O modelo
resultante da análise é uma abstração concisa e precisa do que o sistema desejado
deve fazer, e não como será feito. Os componentes do modelo devem retratar
conceitos do domínio da aplicação, e não conceitos de implementação tais como
estruturas de dados ou tabelas de um banco de dados relacional. Um bom modelo
deve ser compreendido e criticado por especialistas do domínio da aplicação que
3

não sejam, necessariamente, programadores. O modelo de análise não deve conter


quaisquer decisões de implementação.
• Projeto. O projeto é uma extensão e adaptação técnica dos resultados da análise. O
projeto pode ser dividido em (1)projeto da arquitetura, ou projeto do sistema, e
(2)projeto detalhado, ou projeto de objetos. Durante o projeto do sistema, o sistema
é organizado em subsistemas, com base na estrutura da análise e na arquitetura
proposta. O projetista do sistema deve decidir quais características de performance
devem ser otimizadas, escolher as estratégias do projeto e fazer alocação dos
recursos necessários. O projetista de objetos, por sua vez, constrói um modelo de
projeto baseado no modelo de análise, porém acrescido e modificado com detalhes
de implementação. O projetista adiciona os detalhes no modelo de projeto de
acordo com a estratégia estabelecida durante o projeto do sistema. O foco do
projeto de objetos é definir as estruturas de dados e os algoritmos necessários para
implementar cada componente do sistema. Os resultados da análise ainda são
significativos, porém aumentados com estruturas de dados e algoritmos do
domínio da computação, escolhidos para otimizar o desempenho do sistema.
Objetos do domínio da aplicação e objetos do domínio da computação, embora
existindo em planos conceituais distintos, são descritos através dos mesmos
conceitos e notações orientados a objetos. O modelo resultante do projeto é uma
abstração concisa e precisa do que o sistema proposto vai fazer e como vai fazer.
• Implementação. Os objetos e relacionamento desenvolvidos durante o projeto de
objetos são finalmente implementados em uma linguagem de programação, banco
de dados ou hardware. A implementação deveria ser uma atividade meramente
mecânica dentro do ciclo de desenvolvimento do sistema, porque todas as decisões
difíceis deveriam ser tomadas na fase de projeto. É claro que a linguagem alvo
influencia muitas decisões de projeto, mas o projeto não deveria ser dependente de
idiossincrasias de uma linguagem de programação. Durante a implementação, é
importante seguir as boas práticas da engenharia de software, para que o sistema
final permaneça flexível e extensível.
• Teste. O objetivo do teste é identificar os erros do sistema. Quando um erro é
encontrado , o teste é considerado um sucesso, e não uma falha. Um teste consiste
de um número de casos de teste, onde são verificados diferentes aspectos da parte
do sistema sendo testada. Cada caso de teste diz o que fazer, quais dados utilizar e
quais resultados esperar. O processo de teste está tornando-se cada vez mais
automatizado, incluindo ferramentas para especificação e execução dos testes bem
como para administração do processo geral de teste. Há diferentes tipos de teste [1,
página 285], tais como o teste unitário, teste de integração, teste de sistema e teste
de regressão. Detalhes sobre teste de software está fora do escopo deste texto,
podendo ser encontrados na literatura especializada.

1.3 Organização do Texto


Na Seção 2, discutiremos, de forma prática, os principais conceitos e propriedades da
orientação a objetos. Na Seção 3, apresentaremos um resumo dos principais diagramas de
uma linguagem gráfica chamada UML — Unified Modeling Language [1,2] —, a linguagem
que utilizaremos para descrever os modelos de um sistema de computador. Na Seção 4,
trataremos dos tópicos relacionados a projetos de sistemas orientados a objetos.
2 O que são Objetos?
Em sistemas de programação estruturada, dados e procedimentos são tratados como entidades
separadas, sendo o programador responsável pela aplicação de procedimentos ativos a
estruturas de dados passivas e, freqüentemente, pela garantia da adequação entre
procedimentos e tipos de dados aos quais esses procedimentos são aplicados. Em programas
orientados a objetos, ao contrário, a modelagem do domínio da aplicação é feita de acordo
com uma visão tão próxima quanto possível do mundo real, através de objetos que se
comunicam por troca de mensagens.

OBJETOS Objetos são um conjunto de dados mais um conjunto de procedimentos que


representam a estrutura e o comportamento de uma entidade concreta ou abstrata.
Os dados, ou atributos, são informações que descrevem a estrutura do objeto. Os
procedimentos, ou métodos, são operações pré-definidas que acessam os atributos e
respondem sobre o comportamento do objeto.

Nessa Seção discutiremos os conceitos básicos e as propriedades mais importantes da


orientação a objetos. Estes conceitos são independentes de uma linguagem de programação
em particular, orientada a objetos ou não. (Podemos programar orientado a objetos sem
utilizar, necessariamente, uma linguagem de programação orientada a objetos.) No entanto,
guiaremos nossas discussões com exemplos de trechos de programas em Object Pascal (...), a
linguagem de programação do Delphi.

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.

2.2 Construindo Objetos de uma Classe em Object Pascal


Em Object Pascal, construímos objetos de uma classe executando métodos especiais,
declarados na classe, chamados construtores. Um método construtor aloca memória para
armazenar o objeto sendo construído, executa as instruções de inicialização definidas na
implementação do método e retorna um ponteiro para o objeto construído. Um objeto gerado
por uma classe é chamado instância da classe. Podemos dizer, por outro lado, que um objeto é
uma instância de uma classe.
Na classe tPoint temos dois métodos construtores, chamados Create e Construct,
linhas 5-6 do Programa 2.1. (Em Object Pascal, não podemos ter, dentro de uma mesma
classe, dois métodos como o mesmo nome.) Antes de criarmos um objeto da classe tPoint,
vamos declarar uma variável da classe:
var
p: tPoint;
Temos, nesse ponto, a variável p, do tipo tPoint (note que ainda não criamos objeto algum).
Agora, vamos criar um objeto da classe tPoint, utilizando o construtor da linha 5 do
Programa 2.1, e atribuir o ponteiro retornado pelo construtor à variável p:
p := tPoint.Create;

Observe atentamente a sentença acima. O construtor Create inicializa os atributos x e y de


uma nova instância da classe Point com zero, conforme as linhas 12-16 do Programa 2.2, e
retorna um ponteiro para o objeto construído, o qual é atribuído à variável p. Note, porém, que
a variável p foi declarada como sendo do tipo tPoint, e não do tipo ponteiro para tPoint. A
sentença não contém erros porque, no Delphi, toda variável do tipo objeto não é, na verdade,
um objeto, mas sim um ponteiro (implícito) para um objeto.
Se quiséssemos, poderíamos utilizar o construtor da linha 6 do Programa 2.1 para
construir um objeto da classe tPoint, como no exemplo a seguir:
p := tPoint.Construct(100, 100);
6

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

2.3 Enviando Mensagens a um Objeto


Quando quisermos que um objeto execute alguma operação, enviamos uma mensagem ao
objeto. Uma mensagem, portanto, é uma solicitação a algum objeto para que o objeto execute
alguma operação (a operação corresponde a um método declarado na classe do objeto). Uma
mensagem é caracterizada por um receptor, um seletor e por um conjunto, eventualmente
vazio, de parâmetros. Por exemplo, na sentença
q.Copy(p);

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

Na primeira, estamos criando um objeto da classe tPoint; na segunda, enviando uma


mensagem ao objeto q. Observe, na sentença de criação do objeto, que tudo se passa como se
estivéssemos enviando a mensagem Construct para tPoint, com a diferença que tPoint
não é um objeto, mas sim uma classe de objetos. Na verdade, em Object Pascal, uma classe é
um objeto, e por isso podemos mandar algumas mensagens para uma classe, tais como
àquelas correspondentes aos métodos construtores declarados na classe.2
2.4 Destruindo Objetos em Object Pascal
Vamos declarar duas outras classes, tLine e tFigure, Programas 2.3 e 2.4, respectivamente.
34 type
35 tLine = class
36 Figure: tFigure;
37 Next, Prev: tLine;
38 P1: tPoint;
39 P2: tPoint;
40 constructor Create(f: tFigure);
41 constructor Construct(f: tFigure; p1, p2: tPoint);
42 destructor Destroy;
43 procedure Draw(c: TCanvas);
44 end;
Programa 2.3 Declaração da classe tLine.

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.

54 procedure tFigure.Draw(c: TCanvas);


55 var
56 line: tLine;
57 begin
58 line := LineList;
59 while line <> nil do
60 begin
61 line.Draw(c);
62 line := line.Next;
63 end;
64 end;
Programa 2.5 Implementação do método tFigure.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.

65 construtor tLine.Construct(f: tFigure; p1, p2: tPoint);


66 begin
67 if f <> nil then
68 f.Add(self)
69 else
70 begin
71 Next := nil;
72 Prev := nil;
73 end;
74 self.P1 := tPoint.Create(p1.x, p1.y);
75 self.P2 := tPoint.Create(p2.x, p2.y);
76 end;
Programa 2.6 Implementação do construtor tLine.Construct.

3
TCanvas é uma classe da VCL (Visual Component Library) do Delphi.
8

A novidade no Programa 2.3 e no Programa 2.4 é a presença dos métodos destrutores


tLine.Destroy e tFigure.Destroy, linhas 42 e 49, respectivamente. Um método destrutor é
responsável pela “limpeza da casa” e liberação da memória utilizada por um objeto. Por
exemplo, se line for uma variável da classe tLine, a sentença
line.Destroy;
destrói o objeto apontado por line. Note que a definição de um destrutor para uma classe só é
de fato necessária se precisarmos “limpar a casa” antes de um objeto da classe ser destruído.
A classe tPoint, por exemplo não possuí um destrutor. Nesse caso, para destruirmos o objeto
apontado (implicitamente) por uma variável p da classe tPoint, fazemos:
p.Free;

(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

Observemos o Programa 2.10. O argumento dos métodos Add e Remove da classe


tPrimitive (linhas 113-114), bem como o atributo PrimitiveList (linha 109), não são do
tipo específico tLine ou tRectangle, mas sim do tipo genérico tPrimitive. Em Object
Pascal, podemos atribuir a uma variável de uma classe base o endereço de um objeto de uma
classe derivada. Significa que, e isso é muito importante, se p for uma variável da classe
tPrimitive e line for uma variável da classe tLine, podemos escrever a sentença
p := line;
porque tLine é derivada de tPrimitive (o contrário não é verdadeiro). É por esse motivo
que a classe tFigure funciona.
A classe tPrimitive, declarada a partir da linha 117 do Programa 2.10, contém os
atributos e métodos comuns de qualquer primitivo gráfico de uma figura (os atributos P1 e P2
não estão incluídos porque são específicos de linhas e retângulos). A palavra reservada
protected, na linha 118, indica que o atributo Figure é protegido, ou seja, só é visível em
métodos da classe tPrimitive ou em métodos das classes derivadas de tPrimitive.
(Explicaremos o significado de virtual e abstract na próxima Seção.) A declaração da
classe tLine, derivada de tPrimitive, é mostrada no Programa 2.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.

139 construtor tLine.Create(f: tFigure);


140 begin
141 inherited Create(f);
142 P1 := nil;
143 P2 := nil;
144 end;
Programa 2.12 Implementação do novo construtor tLine.Create.

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.

A sobrecarga de operadores e identificadores de métodos pode nos ajudar a escrever


um programa mais claro e, portanto, mais fácil de compreender e compartilhas com outros
desenvolvedores. Contudo, a característica mais notável do polimorfismo em sistemas de
programação orientada a objetos é o acoplamento dinâmico (ou junção posterior). Nesse caso,
o tipo de um objeto receptor de uma mensagem só é conhecido em tempo de execução do
programa e, portanto, o compilador não pode decidir, em tempo de compilação, qual método
de qual classe acoplar à mensagem.
Acoplamento dinâmico e herança são conceitos associados. Tomemos como exemplo
o Programa 2.14. O Programa é quase idêntico ao Programa 2.13; a única diferença é que a
variável p é declarada, na linha 155, como sendo da classe tPrimitive. O Programa está
correto porque, como já sabemos, podemos atribuir a uma variável de uma classe base o
endereço de um objeto de uma classe derivada. Nesse caso, qual método será acoplado à
mensagem da linha 159: Draw da classe tPrimitive ou Draw da classe tLine? Conforme
vimos anteriormente, o método acoplado deveria ser o método Draw da classe tPrimitive,
pois p é uma variável da classe tPrimitive.

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.

Anda mungkin juga menyukai