Você está na página 1de 488

Página em branco

FUNDAMENTOS DO
DESENHO ORIENTADO A
OBJETO COM UML

Meilir Page-Jones

Tradução:
Celso Roberto Paschoa

Revisão Técnica:
José Davi Furlan
Consultor em UML e autor da Makron Books

MAKRON Books Ltda.


Rua Tabapuã, 1.348, Itaim-Bibi
CEP 04533-004 – São Paulo – SP
(11) 3849-8604 e (11) 3845-6622
e-mail: makron@books.com.br

São Paulo • Rio de Janeiro • Ribeirão Preto • Belém • Belo Horizonte • Brasília • Campo Grande
• Cuiabá • Curitiba • Florianópolis • Fortaleza • Goiânia • Manaus • Natal • Porto Alegre •
Recife • Salvador

Brasil • Argentina • Colômbia • Costa Rica • Chile • Espanha • Guatemala • México • Peru
• Porto Rico • Venezuela
Do original: Fundamentals of Object-Oriented Design in UML
Copyright © 2000 by Meilir Page-Jones. Original em inglês publicado pelo
acordo com a editora Addison Wesley Longman, Uma companhia da Person
Education.
Copyright © 2001 MAKRON Books Ltda.

Todos os direitos para a língua portuguesa reservados pela MAKRON Books


Ltda. Nenhuma parte desta publicação poderá ser reproduzida, guardada pelo
sistema “retrieval” ou transmitida de qualquer modo ou por qualquer outro
meio, seja este eletrônico, mecânico, de fotocópia, de gravação ou outros,
sem prévia autorização, por escrito, da Editora.

EDITOR: MILTON MIRA DE ASSUMPÇÃO FILHO

Gerente de Produção
Silas Camargo
Editora Assistente
Eugênia Pessotti
Produtora Editorial
Salete Del Guerra

Editoração Eletrônica: ERJ Informática Ltda.

Dados de Catalogação na Publicação


Fundamentos do Desenho Orientado a Objeto com UML
Tradução: Celso Roberto Paschoa; Revisão
Técnica: José Davi Furlan

São Paulo: MAKRON Books, 2001

ISBN: 1243-9
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
AGRADECIMENTOS
A
gradecimentos

S e eu fosse agradecer a todas as pessoas que deveria, a listagem dos nomes


merecedores de tal honra iria até a página 94. Assim, em vez de agradecer
um a um, faço um agradecimento geral ao enorme grupo de colegas que me
inspiraram, ensinaram e me corrigiram durante todos esses anos. Mas alguns
deles merecem um obrigado muito especial:
• Os experts que fizeram seus comentários e me proporcionaram novas
idéias, quer na primeira ou na segunda edição deste livro: Jim Bea-
ver, Bob Binder, Ken Boyer, Tom Bragg, Tom Bruce, Martin Fowler,
Alan Hecht, Mike Frankel, Cris Kobryn, Jim Odell, Tim Ottinger, An-
gel Rodriguez, Ben Sano, Mike Silves, Jess Thompson, Lynwood Wil-
son e Becky Winant. As sugestões deles foram em geral proveitosas,
muitas vezes incitadoras de idéias, e algumas vezes hilárias. Graças
a esses comentários, evitei um bocado de erros tolos e fiz vários aper-
feiçoamentos significativos. Graças aos incentivos deles, continuei
meu trabalho para aperfeiçoar este livro.
• Kendall Scott, por sua excepcional dedicação ao manuscrito.
• Steve Weiss, que, juntamente com Walter Beck, me convenceu a vol-
tar meus conhecimentos à orientação a objeto, muito antes de esse as-
sunto estar na moda. Se não fosse por Steve, eu nunca teria me
envolvido neste livro.
• Larry Constantine, que acertou o alvo em cheio com seus princípios
de desenho de software.
• Stan Kelly-Bootle, grande contador de anedotas e de histórias, bon vi-
vant, lingüista astucioso, criador da coluna Shameless Chutzpah

V
VI FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

(Atrevimento Descarado) e autor de Ni Fydd Mamog Arall Byth


(There’ll Never Be Another You [Nunca Haverá Alguém como Você],
entre outros romances históricos galeses), por fornecer-me seu
apoio. (Stan, eu lhe retribuirei seu apoio muito em breve.)
• Bert Weedon, que enriqueceu notavelmente a arte da música tanto
quanto a ciência da astrofísica computacional.
• Ed Yourdon e Tim Lister, por sempre terem confiado no meu traba-
lho.
• Bertrand Meyer, que permanece como uma contínua fonte de inspira-
ção.
• Paul Becker, pelo seu encorajamento durante esses anos.
• Rolando Racko, por compartilhar comigo suas reflexões.
• Os 97 autores de trabalhos ligados à orientação a objeto cujos livros
repousam, parcialmente lidos, em minha estante.
• Ao “Jolt Panel” da revista Software Development, pertencente a Miller
Freeman, Inc., que me agraciou em 1996 com um Prêmio de Produti-
vidade referente à primeira edição deste livro.
• Nuno Andrade, Michael Richter, Michael Lumelsky, David McClin-
tock, Wendy Eakin, assim como o restante da equipe na Dorset House
Publishing, bibliófilos e conhecedores da língua de grande renome,
que trabalharam com enorme afinco e com o melhor da tecnologia
mais avançada. A tarefa deles —, com o problema nada trivial de re-
mover completamente silepses1, lítotes2, palavras ambíguas, senten-
ças difíceis de se compreender iniciando com gerúndios que
persistiam em ocorrer, tempos de verbos incorretos e até mesmo ad-
jetivos inadequados e desnecessários —, criou uma redação profissio-
nal a partir de um manuscrito rudimentar. Enfim, foi um trabalho
heróico e excelente!
• Carter Shanklin e a equipe da Addison Wesley Longman, pela contri-
buição de colocar esta edição do livro no mercado.
• Meus clientes em meu trabalho de consultoria, e meus alunos parti-
cipam de conferências no mundo inteiro, que compartilharam suas ex-

1. N.T.: Silepse — construção de linguagem em que a concordância das palavras se faz pelo sen-
tido e não segundo as regras de sintaxe.
2. N.T.: Lítotes — construção de linguagem em que a frase afirmativa é feita pela negação do
contrário.
AGRADECIMENTOS VII

periências comigo e fizeram o melhor que puderam para que eu me


mantivesse coerente com meus princípios.
• Minha família, que manteve firme sua paciência, enquanto convivia
com um “troglodita rabiscador de livros”.
Finalmente, nenhum agradecimento poderia ser completo sem o reconhe-
cimento de duas pessoas que há muito tempo têm se distinguido de forma ím-
par em nosso ramo de atuação:
• O catedrático Sid Dijkstra, cuja contribuição à engenharia de softwa-
re é impossível de se medir.
• Kedney Dogstar, o lendário Cowboy da Codificação e inventor da De-
puração Extrema, que já furou mais filas na hora do almoço na
Bramston Capra Consulting que a maioria de nós conseguirá furar
em toda a existência.

NOTA DO AUTOR:
Nenhum metodologista foi prejudicado ao se fazer este livro.
DEDICATÓRIA
A minha família
SUMÁRIO
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
S umário

Apresentação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XIX

Parte I — Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1. Afinal de Contas, o Que Significa Ser Orientado a Objeto? . . . . . . . 1
1.1 Encapsulamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2 Ocultação de Informações e Implementações . . . . . . . . . . . . . . . . . . . . . . 13
1.3 Retenção de Estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.4 Identidade de Objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.5 Mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.5.1 Estruturas de mensagens . . . . . . . . . . . . . . . . . . . . 20
1.5.2 Argumentos de mensagens . . . . . . . . . . . . . . . . . . . 21
1.5.3 Os papéis dos objetos em mensagens . . . . . . . . . . . . . . 23
1.5.4 Tipos de mensagem . . . . . . . . . . . . . . . . . . . . . . . 25
1.6 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.7 Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
1.8. Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.9 Generalização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1.10 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
1.11 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
1.12 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
2. Breve História da Orientação a Objeto . . . . . . . . . . . . . . . . . . 58
2.1 De Onde Surgiu a Orientação a Objeto? . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.1.1 Larry Constantine . . . . . . . . . . . . . . . . . . . . . . . . 59
2.1.2 O.-J. Dahl e K. Nygaard . . . . . . . . . . . . . . . . . . . . 59
2.1.3 Alan Kay, Adele Goldberg e outros . . . . . . . . . . . . . . . 59
2.1.4 Edsger Dijkstra . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.1.5 Barbara Liskov . . . . . . . . . . . . . . . . . . . . . . . . . . 60
IX
X FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2.1.6 David Parnas . . . . . . . . . . . . . . . . . . . . . . . . . . . 60


2.1.7 Jean Ichbiah e outros . . . . . . . . . . . . . . . . . . . . . . 60
2.1.8 Bjarne Stroustrup . . . . . . . . . . . . . . . . . . . . . . . . 60
2.1.9 Bertrand Meyer . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.1.10 Grady Booch, Ivar Jacobson e Jim Rumbaugh . . . . . . . . . 61
2.2 A Orientação a Objeto Atinge a Maioridade . . . . . . . . . . . . . . . . . . . . . . . 62
2.3 Orientação a Objeto Como Disciplina de Engenharia . . . . . . . . . . . . . . . 64
2.4 Para Que Serve a Orientação a Objeto? . . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.4.1 Análise dos requisitos de usuários . . . . . . . . . . . . . . . 67
2.4.2 Desenho de software . . . . . . . . . . . . . . . . . . . . . . . 67
2.4.3 Construção de software . . . . . . . . . . . . . . . . . . . . . 68
2.4.4 Manutenção de software . . . . . . . . . . . . . . . . . . . . . 71
2.4.5 Utilização de software . . . . . . . . . . . . . . . . . . . . . . 72
2.4.6 Gerenciamento de projetos de software . . . . . . . . . . . . . 73
2.5 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
2.6 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
2.7 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

Parte II — the Unified Modeling Language . . . . . . . . . . . . . . . . 79


3. Expressão Básica para Classes, Atributos e Operações . . . . . . . . . 87
3.1 A Classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.2 Atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
3.3 Operações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
3.4 Sobreposição de Operações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3.5 Visibilidade de Atributos e Operações . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
3.6 Atributos e Operações de Classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
3.7 Operações e Classes Abstratas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
3.8 O Utilitário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
3.9 Classes Parametrizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
3.10 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
3.11 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
3.12 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4. Diagramas de Classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.1 A Construção da Generalização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.1.1 Herança simples . . . . . . . . . . . . . . . . . . . . . . . . 109
4.1.2 Herança múltipla . . . . . . . . . . . . . . . . . . . . . . . . 111
4.1.3 Divisão em subclasses . . . . . . . . . . . . . . . . . . . . . 111
4.1.4 Discriminadores de particionamento . . . . . . . . . . . . . 114
4.2 A Construção de Associação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
4.2.1 Notação básica da UML para associações . . . . . . . . . . 118
4.2.2 Associações representadas como classes . . . . . . . . . . . 120
4.2.3 Associações de ordem mais alta . . . . . . . . . . . . . . . . 122
SUMÁRIO XI

4.2.4 Navegabilidade de associações . . . . . . . . . . . . . . . . 123


4.3 Associações Todo/Parte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
4.3.1 Composição . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
4.3.2 Agregação . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
4.4 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
4.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
4.6 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
5. Diagramas de Interação entre Objetos . . . . . . . . . . . . . . . . . . 138
5.1 Diagrama de Colaboração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
5.1.1 Representando uma mensagem . . . . . . . . . . . . . . . . 140
5.1.2 Polimorfismo no diagrama de colaboração . . . . . . . . . . 143
5.1.3 Mensagens interativas . . . . . . . . . . . . . . . . . . . . . 144
5.1.4 Uso do self (auto) em mensagens . . . . . . . . . . . . . . . 146
5.2 Diagrama de Seqüência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
5.3 Mensagens Assíncronas e Execução Concorrente . . . . . . . . . . . . . . . . . . 151
5.3.1 Representando uma mensagem assíncrona . . . . . . . . . 152
5.3.2 O mecanismo de recado (callback) . . . . . . . . . . . . . . 153
5.3.3 Mensagens assíncronas com prioridade . . . . . . . . . . . 158
5.3.4 Representando uma mensagem de difusão (broast)
[nontargeted] . . . . . . . . . . . . . . . . . . . . . . . . . . 159
5.4 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
5.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
5.6 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
6. Diagramas de Estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
6.1 Diagramas de Estado Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
6.2 Estados Aninhados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
6.3 Estados Concorrentes e Sincronização . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
6.4 Estados Transientes de Argumentos de Mensagens de Saída . . . . . . . 178
6.5 Atributos Continuamente Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
6.6 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
6.7 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
6.8 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
7. Arquitetura e Diagramas de Interface . . . . . . . . . . . . . . . . . . 191
7.1 Representação da Arquitetura de Sistemas . . . . . . . . . . . . . . . . . . . . . . 191
7.1.1 Pacotes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
7.1.2 Diagramas de implantação para artefatos de hardware . . 194
7.1.3 Diagramas de implantação para construções de software . . 196
7.2 Representando a Interface Humana . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
7.2.1 O diagrama de layout de janelas . . . . . . . . . . . . . . . 200
7.2.2 Diagrama de navegação de janelas . . . . . . . . . . . . . . 202
7.2.3 Uma breve digressão: o que a orientação a objeto tem em
comum com a GUI? . . . . . . . . . . . . . . . . . . . . . . 205
XII FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

7.3 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206


7.4 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
7.5 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208

Parte III — Os Princípios do Desenho Orientado a Objeto . . . 211


8. Encapsulamento e Congeneridade . . . . . . . . . . . . . . . . . . . . 213
8.1 Estrutura de Encapsulamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
8.1.1 Níveis de encapsulamento . . . . . . . . . . . . . . . . . . . 214
8.1.2 Critérios de desenho governando níveis interativos
de encapsulamento . . . . . . . . . . . . . . . . . . . . . . . 216
8.2 Congeneridade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
8.2.1 Variedades de congeneridade . . . . . . . . . . . . . . . . . 219
8.2.2 Contrageneridade . . . . . . . . . . . . . . . . . . . . . . . 224
8.2.3 Congeneridade e fronteiras de encapsulamento . . . . . . . 225
8.2.4 Congeneridade e manutenção . . . . . . . . . . . . . . . . . 226
8.2.5 Abusos de congeneridade em sistemas orientados a objeto . 229
8.2.6 O termo congeneridade . . . . . . . . . . . . . . . . . . . . 232
8.3 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
8.4 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
8.5 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
9. Domínios, Grau de Dependência e Coesão . . . . . . . . . . . . . . . 237
9.1 Domínios de Classe de Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
9.1.1 O domínio de base . . . . . . . . . . . . . . . . . . . . . . . 238
9.1.2 O domínio de arquitetura . . . . . . . . . . . . . . . . . . . 239
9.1.3 O domínio de negócio . . . . . . . . . . . . . . . . . . . . . 240
9.1.4 O domínio de aplicação . . . . . . . . . . . . . . . . . . . . 241
9.1.5 A origem das classes em cada domínio . . . . . . . . . . . . 242
9.2 Grau de dependência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
9.2.1 O que é grau de dependência? . . . . . . . . . . . . . . . . 245
9.2.2 A utilização do grau de dependência . . . . . . . . . . . . . 248
9.2.3 A Lei de Deméter . . . . . . . . . . . . . . . . . . . . . . . 249
9.3 Coesão de Classe: Uma Classe e Suas Características . . . . . . . . . . . . . 250
9.3.1 Coesão de instância mista . . . . . . . . . . . . . . . . . . . 251
9.3.2 Coesão de domínio misto . . . . . . . . . . . . . . . . . . . 253
9.3.3 Coesão de papel misto . . . . . . . . . . . . . . . . . . . . . 255
9.4 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
9.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
9.6 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
10. Espaço-estado e Comportamento . . . . . . . . . . . . . . . . . . . . . 263
10.1 Espaço-Estado e Comportamento de uma Classe . . . . . . . . . . . . . . . . . . 263
10.2 O Espaço-Estado de uma Subclasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
SUMÁRIO XIII

10.3 O Comportamento de uma Subclasse . . . . . . . . . . . . . . . . . . . . . . . . . . . 270


10.4 A Invariante de Classe Como Restrição em um Espaço-Estado . . . . . . 271
10.5 Precondições e Pós-condições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
10.6 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
10.7 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
10.8 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
11. Conformidade de Tipo e Comportamento Fechado . . . . . . . . . . 283
11.1 Classe versus Tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
11.2 O Princípio da Conformidade de Tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
11.2.1 Os princípios da contravariação e covariação . . . . . . . . 287
11.2.2 Um exemplo de contravariação e covariação . . . . . . . . . 288
11.2.3 Uma ilustração gráfica de contravariação e covariação . . . 293
11.2.4 Resumo dos requisitos para conformidade de tipo . . . . . . 296
11.3 O Princípio do Comportamento Fechado . . . . . . . . . . . . . . . . . . . . . . . . . 297
11.4 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
11.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
11.6 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
12. Os Perigos da Herança e do Polimorfismo . . . . . . . . . . . . . . . 306
12.1 Abusos da Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
12.1.1 Conjuntos equivocados . . . . . . . . . . . . . . . . . . . . . 307
12.1.2 Hierarquia invertida . . . . . . . . . . . . . . . . . . . . . . 308
12.1.3 Confundir classe com instância . . . . . . . . . . . . . . . . 309
12.1.4 Utilização inadequada . . . . . . . . . . . . . . . . . . . . . 313
12.2 O Perigo do Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
12.2.1 Polimorfismo de operações . . . . . . . . . . . . . . . . . . . 317
12.2.2 Polimorfismo de variáveis . . . . . . . . . . . . . . . . . . . 320
12.2.3 Polimorfismo em mensagens . . . . . . . . . . . . . . . . . 322
12.2.4 Polimorfismo e generalidade . . . . . . . . . . . . . . . . . 324
12.3 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
12.4 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
12.5 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
13. Técnicas para Organizar Operações . . . . . . . . . . . . . . . . . . . 335
13.1 Classes Mistas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
13.1.1 Exemplo de negócio . . . . . . . . . . . . . . . . . . . . . . 335
13.1.2 Um exemplo gráfico . . . . . . . . . . . . . . . . . . . . . . 340
13.2 Anéis de Operações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
13.3 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
13.4 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
13.5 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
14. Coesão de Classe, Suporte de Estados e de Comportamentos . . . . 355
14.1 Suporte de Estados em uma Interface de Classe . . . . . . . . . . . . . . . . . . 356
XIV FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

14.2 Suporte de Comportamentos em uma Interface de Classe . . . . . . . . . . 358


14.3 Coesão de Operações em uma Interface de Classe . . . . . . . . . . . . . . . . . 366
14.4 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
14.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
14.5 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
Desenhando um Componente de Software . . . . . . . . . . . . . . . . . . 384
15.1 O Que É um Componente? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
15.2 Similaridades e Diferenças entre Componentes e Objetos . . . . . . . . . . 387
15.3 Exemplo de um Componente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
15.4 Desenho Interno de um Componente . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
15.5 Componentes Leves e Pesados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
15.6 Vantagens e Desvantagens de Se Utilizarem Componentes . . . . . . . . . 409
15.7 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
15.8 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
15.9 Respostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
Apêndice A — Lista de Conferência para um Ensaio de Desenho
Orientado a Objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Questões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Apêndice B — O Manual do Proprietário de Desenho Orientado a
Objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425
Apêndice C — O Guia Relâmpago para a Terminologia Orientada a
Objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
Glossário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434
Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
Índice Analítico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
P refácio

PREFÁCIO
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O s objetos tornaram-se os blocos de construção onipresentes do software


moderno, e a orientação a objeto é o paradigma penetrante da prática de
engenharia de software contemporânea. Livros sobre esse ou aquele assunto
orientado a objeto existem às dúzias, mas, quando a primeira edição deste li-
vro, sob o nome What Every Programmer Should Know about Object-Oriented
Design (O Que Todo Programador Deveria Saber sobre Desenho Orientado a
Objeto) foi publicada, ela foi imediatamente reconhecida como uma contribui-
ção original, reflexiva e valiosa, de um dos autores mais lidos e de um dos pen-
sadores mais consistentemente brilhantes existentes na esfera do
desenvolvimento de software nos dias de hoje.
Esta segunda edição, totalmente revisada e renomeada, estende os fun-
damentos, expande o material e atualiza a notação para criar uma referência
de valor imediato e duradouro. Ela foi complementada com novas reflexões
quanto ao avanço orientado a objeto, aos usos e abusos de utilização da heran-
ça e à maneira de modelar relacionamentos de dados problemáticos em classes
de objetos. Ela corresponde ao “melhor oferecido” por Page-Jones, represen-
tando seus conhecimentos mais recentes, e de modo detalhado.
O autor está nas trincheiras da linha de frente como consultor e dese-
nhista há décadas, e suas lições, conseguidas com enorme dificuldade, são re-
veladas em cada página deste livro. Eu tenho participado dos trabalhos dele;
mais recentemente como colaborador em um projeto de grande porte, com um
modelo de caso de uso inicial com mais de 340 casos de uso! Conforme o leitor
perceberá, ele é, acima de tudo, um pragmático, cuja atenção em fundamentos

XV
XVI FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

e detalhes se reflete em seu trabalho de análise e desenho, bem como na re-


dação do mesmo.
A verdade é que Page-Jones é um professor talentoso, que tem o dom de
apanhar idéias complexas, muitas vezes malcompreendidas, e lançar uma luz
conceitual sobre elas, tornando-as amplamente destacadas em um claro relevo
em relação às sombras obscuras. Ele pode ter um monte de problemas e en-
volvê-los de tal maneira em um único exemplo padrão que os faz parecerem
tão óbvios que o restante de nós fica se perguntando como um dia poderíamos
ter deixado de ver tudo isso. O que você faria quando se iniciasse a época da
ordenha na fazenda leiteira orientada a objeto? Você enviaria uma mensagem
para o objeto Vaca tirar o seu próprio leite ou uma mensagem para o objeto
Leite sair ele próprio da vaca? Um momento de reflexão, e a necessidade de
um administrador de eventos para coordenar a ordenha de leite torna-se ex-
tremamente clara. Seus exemplos esclarecedores, tal como um extraído de
uma apresentação de relação de conferências ou de enigmas do tipo “Pessoa
possui Cachorro”, tornaram-se parte do folclore inerente da orientação a objeto.
Na realidade, este livro demonstra habilmente como os princípios há mui-
to estabelecidos de desenho robusto e sadio, já amplamente utilizados pelos
profissionais praticantes, podem ser transportados e adaptados para sistemas
orientados a objeto em desenvolvimento nas novas linguagens mais avançadas
e nos contextos mais desafiadores. Edificando a partir desses fundamentos, o
livro mantém um enfoque inflexivelmente pragmático baseado na experiência
do mundo real, destilando a essência dessa experiência na forma de exemplos
compactos que orientarão o desenvolvedor, seja ele novato ou experiente no
ramo, para as melhores soluções de software orientado a objeto.
Page-Jones se baseia em uma ampla experiência com desenvolvimento
orientado a objeto, na qualidade de consultor, professor e metodologista. Ele
foi co-desenvolvedor do método Synthesis, uma das primeiras abordagens sis-
temáticas referentes à análise e ao desenho orientados a objeto, e nós dois ser-
vimos de colaboradores na criação da influente Uniform Object Notation
(Notação Uniforme de Objeto), cujas características podem ser encontradas
nos dias de hoje refletidas e incorporadas em diversos métodos e notações
orientados a objeto. O legado de nosso trabalho pode até mesmo ser reconhe-
cido na Unified Modeling Language — UML (Linguagem de Modelagem Uni-
ficada), que tem sido adotada como o padrão industrial de facto e é utilizada
para ilustrar e clarificar exemplos existentes neste livro.
PREFÁCIO XVII

Aqui você encontrará tudo aquilo de que se precisa para começar a domi-
nar os fundamentos do desenho orientado a objeto. Não só as técnicas básicas
de desenho e construção com objetos estão explicadas com uma clareza excep-
cional, como também estão ilustradas com um enorme número de exemplos, e
elaboradas com discussões sobre os prós e contras de se dispor de bons siste-
mas orientados a objeto. O resto é com você!

Larry Constantine
Rowley, Massachusetts Co-autor do livro Software for Use:
A Practical Guide to the Models and
Methods of Usage-Centered Design
(Reading, Mass.: Addison-Wesley, 1999)
Página em branco
“Vocês dizem que querem algum tipo de evolução.
Bem, vocês sabem, eu estou fazendo o que posso.”
Charles Darwin, A Origem das Espécies

FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML


APRESENTAÇÃO
A presentação

A lgumas pessoas que leram este livro, quando ele ainda era apenas um
rascunho, levantaram algumas questões que talvez sejam interessantes
também para você. Permita-me analisar algumas delas.

Eu sou um programador. Por que deveria me importar com o desenho?


Todos que escrevem código também desenham código — bem ou mal, conscien-
te ou inconscientemente. Meu objetivo ao escrever este livro foi o de estimular
os profissionais de OO — e a proporção deles aumenta anualmente — a cria-
rem bons desenhos orientados a objeto, de maneira consciente e antes da ela-
boração do código. Para atingir essa meta, introduzi no livro a notação, assim
como os princípios e a terminologia que você e seus colegas poderão utilizar
para avaliar seus desenhos e discuti-los à vontade entre si.

Este livro me ensinará uma linguagem de programação orientada a objeto?


Não, embora eu, ocasionalmente, faça algumas referências mais profundas
bem próximas do código, este não é um livro sobre programação orientada a
objeto.

Mas, se eu estiver aprendendo alguma linguagem orientada a objeto, este


livro me ajudará?
Sim, ele lhe ajudará. Se você atualmente não conhece uma linguagem de pro-
gramação orientada a objeto, poderá iniciar seu conhecimento orientado a ob-
jeto com o Capítulo 1. O conhecimento dos conceitos-chave da orientação a
objeto vai acelerar seu aprendizado de uma linguagem orientada a objeto, e,
espero, revigorar o seu ânimo à medida que você for adentrando em um ter-

XIX
XX FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

ritório desconhecido. Os últimos capítulos do livro, sobre desenho robusto e sa-


dio, ainda lhe assistirão quanto a fazer com que seus programas executem de
maneira bem-sucedida.
Por outro lado, se você for um programador orientado a objeto experiente,
poderá utilizar as Partes II e III do livro para aperfeiçoar as habilidades de
desenho vitais para que você seja um “impecável” desenhista ou programador
profissional de software.

Por que os exemplos de código neste livro não estão em C++?


Escrevi o código neste livro em uma linguagem de minha própria idealização,
a qual é uma combinação de quatro linguagens populares: C++, Eiffel, Java e
Smalltalk. Procedi assim porque há dois tipos de programadores: os que são
fluentes em C++ e os que não são. Se o leitor for um aficionado de C++, então
achará o código do livro fácil de traduzir para essa linguagem. Se ele não es-
tiver familiarizado com C++, então muito provavelmente vai considerar a sin-
taxe misteriosa da linguagem utilizada um tanto perturbadora. Alguns
exemplos são dados em Java porque ela é mais acessível para um programa-
dor não especialista em Java do que C++ é para um programador não espe-
cialista em C++. Eu apreciaria que o leitor se sentisse à vontade neste livro,
seja qual for a linguagem de programação que ele use.

Por que este livro não é dedicado ao desenho de janelas, ícones e menus?
Existem duas razões. A primeira é que não acredito que a orientação a objeto
seja útil somente para o desenho de interfaces gráficas do usuário. A segunda
é que existem muitos livros no mercado dedicados unicamente ao tópico do de-
senho orientado a objeto de janelas. Eu queria que esta obra abordasse tópicos
não muito bem cobertos por outros livros orientados a objeto. Entretanto, no
Capítulo 7, forneço um pouco de notação para o desenho de navegação em ja-
nelas.

Este livro é sobre alguma metodologia?


Não. Como você sabe, uma metodologia de desenvolvimento contém muito
mais do que o desenho. Por exemplo, nela há análise de requisitos, gerencia-
mento de bibliotecas e assim por diante. Ainda, uma metodologia real neces-
sita explicar como as diversas atividades de desenvolvimento se encaixam.
Um bocado de assunto!
Assim, em vez de fazer com que este livro se tornasse tão difuso como
muitos outros sobre orientação a objeto, decidi enfocar um único tópico: dese-
nho orientado a objeto.
APRESENTAÇÃO XXI

Você disse um monte de coisas a respeito do que este livro não trata.
Afinal, do que ele trata?
Ele trata das idéias fundamentais, da notação, da terminologia, dos critérios
e dos princípios do desenho de software orientado a objeto. O software orien-
tado a objeto é um software constituído de objetos e das classes para as quais
eles pertencem. Um objeto é uma construção de software na qual operações
(que são semelhantes a funções ou procedimentos) são organizadas em torno
de um conjunto de variáveis (que funcionam como dados). Uma classe imple-
menta um tipo, o qual define o grupo de objetos pertencentes à essa classe.
As frases despretensiosas anteriores retêm algumas implicações sur-
preendentes para os desenhistas e programadores de software, implicações es-
sas que surgem dos conceitos do desenho de herança, polimorfismo e desenho
de segunda ordem. Porém, visto que você fez uma pergunta específica, deixe-
me oferecer a você uma resposta também específica.
A Parte I do livro (Capítulos 1 e 2) fornece uma introdução à orientação
a objeto. O Capítulo 1 resume os conceitos-chave e desmistifica o “polimorfis-
mo”, a “generalidade” e todos os demais jargões da OO. O Capítulo 2 posiciona
a orientação a objeto na estrutura dos primeiros desenvolvimentos ligados a
software. Se você já for familiarizado com a orientação a objeto (talvez por ter
programado em uma linguagem orientada a objeto), então poderá pular a Par-
te I ou apenas passar os olhos rapidamente por ela.
A Parte II (Capítulos 3 ao 7) aborda a Linguagem de Modelagem Unifi-
cada (UML), que de facto tornou-se a notação padrão para retratar o desenho
orientado a objeto. Passando pela mesma, a Parte II também ilustra muitas
das estruturas que você encontrou nos sistemas orientados a objeto. O Capí-
tulo 3 introduz a UML para retratar classes, juntamente com seus atributos
e operações. O Capítulo 4 trata da UML nos casos de associações, objetos agre-
gados e compostos, assim como hierarquias de subclasses e superclasses. O
Capítulo 5 exibe a UML para mensagens (seqüenciais e assíncronas), enquan-
to o Capítulo 6 refere-se à UML nos casos de diagramas de estado. O Capítulo
7 revisa a UML para a arquitetura de sistemas e para as janelas que formam
uma interface humana.
A Parte III (Capítulos 8 a 14) aborda os princípios do desenho orientado
a objeto com alguma profundidade. O Capítulo 8 fixa a cena com as notações
vitais de congeneridade e encapsulamento de nível-2. O Capítulo 9 explora os
vários domínios a partir dos quais “as classes surgem” e descreve diferentes
graus de coesão de classe. Os Capítulos 10 e 11 são os pilares centrais da Par-
te III, aplicando os conceitos de espaço-estado e comportamento para avaliar
XXII FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

quantitativamente se uma hierarquia de classes é tão robusta quanto exten-


sível.
O Capítulo 12 proporciona um pouco de alívio, pois ele examina desenhos
extraídos de projetos reais, incluindo tanto os casos sutis como alguns mais
exagerados. (O Capítulo 12 trata realmente dos perigos de se utilizar abusi-
vamente a herança e o polimorfismo.) O Capítulo13 examina alguns modos de
se organizar operações dentro de uma dada classe, e explica técnicas de dese-
nho, tais como classes mistas e anéis de operação — que vão melhorar a reu-
tilização e a manutenção de classes.
O Capítulo 14 procura responder à velha pergunta: “O que faz com que
uma classe seja boa”. Ao responder a esta questão, esse capítulo descreve os
vários tipos de interface de classe, desde as horríveis até as sublimes. Uma
classe com interface exemplar será uma implementação valiosa de um tipo de
dado abstrato. Se a classe também obedecer aos princípios fundamentais apre-
sentados nos capítulos iniciais, então ela será robusta, confiável, extensível,
reutilizável e fácil de ser mantida, como jamais ocorreu anteriormente.
O Capítulo 15 completa satisfatoriamente o livro, examinando as carac-
terísticas, juntamente com as vantagens e desvantagens, dos componentes de
software. Ao traçar o desenvolvimento de um componente orientado a objeto
para uma aplicação de negócio, eu recordo alguns dos princípios orientados a
objeto dos capítulos anteriores.
Embora eu tenha acrescentado um grande número de exemplos, diagra-
mas e exercícios para reforçar o que comento no texto principal, devo admitir
que o tema discutido na Parte III às vezes ficou um tanto complicado. Entre-
tanto, decidi não simplificar ou diluir temas importantes. Alguns aspectos do
desenho orientado a objeto são difíceis, e sugerir uma simplificação constitui-
ria má orientação.

Este livro trata de tudo que há a respeito de desenho orientado a objeto?


Duvido muito disso. A cada dia, aprendo mais sobre orientação a objeto, e es-
tou seguro de que o mesmo acontece com você. Na verdade, seria um mundo
monótono se um único livro pudesse nos ensinar tudo sobre desenho orientado
a objeto e nada mais nos deixasse para aprender. E nem tudo neste livro tal-
vez esteja totalmente correto! Eu certamente mudei de opinião sobre um ou
dois itens após ter escrito meus livros anteriores, à medida que fui ficando
mais velho e adquirindo mais conhecimento — bem, de qualquer forma, ao fi-
car mais velho.
Conseqüentemente, embora eu acredite que tenha abordado muitos prin-
cípios importantes de desenho orientado a objeto neste livro, se você estudar
APRESENTAÇÃO XXIII

seriamente a orientação a objeto deve continuar a ler o tanto quanto possível


sobre ela e desafiar sempre o que você tiver lido.

Questão Fundamental, como eles dizem: Este livro é para mim?


Mas que pergunta é essa? Você espera que eu diga “não!”? Porém, falando se-
riamente, este livro é destinado a você se você for — ou estiver prestes a ser
— programador, desenhista, engenheiro de sistemas, ou gerente técnico em
um projeto que utilize técnicas orientadas a objeto. Mesmo se for um novato
na orientação a objeto, você poderá extrair um bocado deste livro lendo a Parte
I, praticando um pouco de programação orientada a objeto, e então se voltando
para as Partes II e III.
Você também deve ler este livro se for um estudante universitário ou um
programador profissional que tenha dominado as técnicas padrão de progra-
mação procedural e estiver buscando horizontes mais amplos. Uma grande
parte do material deste livro é apropriada a cursos de último ano das áreas
de informática ou de engenharia de software orientadas a objeto.
Mas, independentemente do que você possa ser na vida, espero que goste
deste livro e que o aproveite. Boa sorte!

Meilir Page-Jones
Bellevue, Washington
meilir@waysys.com
Página em branco
“Todas as pessoas têm uma gratidão habitual,
e certo fanatismo, em relação a alguns
objetos que por um longo tempo,
continuaram a satisfazê-las.”

William Wordsworth, Lyrical Ballads

P arte I — Introdução

AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO?


1.Afinal de Contas, o Que Significa Ser Orientado a Objeto?
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O termo orientado a objeto é, intrinsecamente, destituído de qualquer sentido.


Objeto é praticamente a palavra mais geral da língua. Consulte qualquer
dicionário, e você encontrará uma definição como esta:

Objeto: Uma coisa apresentada, ou capaz de ser apresentada, aos sen-


tidos.

Em outras palavras, um objeto é quase qualquer coisa!


A palavra orientado, da mesma forma, não ajuda muito. Definida como
direcionado na direção de, ela normalmente desempenha o papel de transfor-
mar o termo orientado a objeto em uma locução adjetiva. Assim, temos:

Orientado a objeto: Direcionado na direção de quase tudo o que você


possa imaginar.

Não é de admirar que a indústria de software historicamente teve difi-


culdades a respeito de uma definição concordante de orientado a objeto. Não
constitui surpresa que essa ausência de clareza já tenha feito com que qual-
quer vendedor ambulante de soft wares clamasse que seus “itens milagrosos”
encerrados de forma retrátil fossem “orientados a objeto”. E não é de sur-
preender que tantos cursos de treinamento sobre “não sei mais quê, e outras
coisas mais orientadas a objeto”, se transformassem em antigos “cursos re-
quentados” ou em simplesmente toda uma “conversa vazia” para simplesmen-
te conquistar aplausos.

Parte I — Introdução
2 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A primeira vez em que adentrei no reino da OO (orientação a objeto), de-


cidi estabelecer uma definição da mesma de uma vez por todas. Eu apanhei
uma dúzia de “eruditos” do mundo orientado a objeto e tranquei-os em um
aposento sem comida ou água. Disse a eles que os deixaria sair somente de-
pois que tivessem chegado a um acordo sobre uma definição que poderia pu-
blicar para o mundo ansioso do software.
Uma hora de gritos e pancadas dentro da sala foi seguida pelo silêncio.
Temendo o pior, cautelosamente abri a porta e dei uma examinada no local
potencialmente sangrento. Os experts estavam vivos, mas sentados distante
uns dos outros, sem se falarem.
Aparentemente, cada expert começou a sessão tentando estabelecer uma
definição da orientação a objeto por meio da prática científica de se respeitar
a vez de cada um dos participantes, em asserções repetidas e em voz alta.
Quando perceberam que isso não levaria a qualquer resultado, cada um deles
concordou em listar as propriedades de um ambiente orientado a objeto que
considerasse indispensáveis. Cada um dos experts criou uma lista de aproxi-
madamente seis a 10 propriedades vitais.
Nesse ponto, eles presumivelmente tinham duas opções: poderiam criar
uma longa lista, que constituiria a união de suas listas individuais, ou pode-
riam criar uma lista mais resumida, que seria o cruzamento de suas listas.
Eles escolheram essa última opção, e fizeram uma pequena lista das proprie-
dades que constavam em todas as listas individuais.
A lista resultante, de fato, era muito pequena constituía-se da palavra en-
capsulamento.
Assim, a abordagem de uma discussão entre um grande número de ex-
perts para se chegar a uma definição sobre orientação a objeto não produziu
grandes resultados. O problema é que o termo orientado a objeto é desprovido
de um significado inerente; portanto, sua definição é totalmente arbitrária.
Todavia, no Capítulo 1, arrisco-me a essa tarefa e lhes forneço minha lista de
propriedades do software as quais constituem uma orientação a objeto. Você
pode confiar em mim e concordar que essa é a lista “correta”, ou pode acredi-
tar que, por pura casualidade, ela contém as nove propriedades mais comuns
selecionadas pelas 12 “celebridades” que ficaram “aprisionadas” naquela sala.
No Capítulo 2, identifico alguns dos criadores da orientação a objeto.
A seguir, analiso algumas atitudes culturais ou sociais predominantes para
com a orientação a objeto, e faço um contraponto desses aspectos com uma dis-
cussão da orientação ao objeto do ponto de vista da engenharia. Concluo o Ca-
pítulo 2 com um breve relato dos benefícios da orientação a objeto para uma
organização de desenvolvimento de software durante cada fase de seu processo.
A final de contas, o que significa
ser orientado a objeto?

C omo mencionei anteriormente, selecionei nove conceitos de software que


considero fundamentais à orientação a objeto. Aqui estão eles:

Encapsulamento
Ocultação de informações e implementações
Retenção de estado
Identidade de objeto
Mensagens
Classes
Herança
Polimorfismo
Generalização

A melhor forma de destacar o significado existente sob esses termos é


usar um pequeno exemplo de código orientado a objeto. À medida que eu for
discutindo esse código durante o capítulo, você descobrirá que o jargão polis-
silábico da orientação a objeto é menos “ameaçador” quanto ao significado do
que realmente aparenta. Na verdade, você provavelmente já deve estar fami-
liarizado com muitas noções da orientação a objeto — se bem que sob nomes
diferentes — devido a suas experiências prévias com software1. Antes de ini-
ciarmos o assunto, temos três observações dispostas na seguinte ordem.

1 Se você estiver curioso a respeito de como a terminologia orientada a objeto passa de uma
linguagem de programação a outra consulte o Apêndice C deste livro.

3
4 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Em primeiro lugar, o código que apresento constitui uma parte de uma


aplicação orientada a objeto muito simples. A aplicação corresponde à exibição
de um hominóide miniatura movendo-se por uma grade sobre uma tela (o tipo
de coisa que talvez você visse em um videogame). Muito embora a orientação
a objeto certamente não fique limitada a aplicações sobre telas, tal tipo de
aplicação proporciona um excelente exemplo de abertura.
Em segundo lugar, uma vez que não me alongo muito na sintaxe ou se-
mântica do código, não se preocupe se o código a princípio não fizer muito sen-
tido. À medida que eu for explicando a terminologia da orientação a objeto,
também explicarei os detalhes do próprio código. Eu escrevi o algoritmo em
um pseudocódigo orientado a objeto, a sintaxe do qual é uma média da sin-
taxe de diversas linguagens orientadas a objeto atuais, tais como C++, Java,
Eiffel e Smalltalk. (Incidentalmente, a construção repeat... until... endrepeat
não guarda qualquer relação com a orientação a objeto. É pura programação
estruturada — um loop com o teste no meio2.)
Em terceiro lugar, muito embora as duas classes não sejam perfeitamen-
te desenhadas, elas são suficientemente boas para nossos propósitos neste ca-
pítulo. Todavia, se você tiver algumas queixas quanto, digamos, a classe
Hominóide, permaneça sintonizado até o Capítulo 9, no qual trato dessa defi-
ciência de desenho. (A deficiência é chamada de “coesão do domínio misto”.)
Agora, vamos observar a aplicação, analisando o memorando de um ge-
rente a ser enviado à sua equipe de desenvolvimento de software.

MEMORANDO

De: Alinah Koad, Gerente de Desenvolvimento de Software

Para: Equipe de Software do Hominóide

Assunto: Software de Controle do Hominóide (V1.0)

Eu acabei de receber uma comunicação vinda dos manda-chuvas dos es-


critórios de carvalho de nossa companhia. Ela informa que ganhamos o con-
trato para o controle do hardware do hominóide. Precisamos fazer um bom
trabalho desta vez, pessoal, para compensar o fiasco do robô “seeing-eye” (vi-
sível a olho nu), que caminhava sob um rolo compressor. Na verdade, os clien-
tes querem que demonstremos nosso software em uma tela de exibição

2 Onde cito código ou pseudocódigo no corpo do texto, utilizo uma fonte similar a esta para
realçá-lo.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 5

(display) antes que nos deixem aplicá-lo no hardware deles. Eles marcaram a
data para o “demo” de nosso software: na próxima segunda-feira.
Na Versão 1 do software, o hominóide teria simplesmente de navegar em
um caminho linear com curvas, como o mostrado abaixo. Você poderia imagi-
ná-lo como alguns blocos quadrados, dispostos para gerar uma trajetória com
largura igual a um bloco, indo desde um quadrado INÍCIO (I) até um quadra-
do FINAL (F). Cada curva ao longo da trajetória seria em ângulo reto, confor-
me mostrado na Figura 1.1.

Figura 1.1 Trajetória através da grade do hominóide.

Um simples avanço feito pelo hominóide faz com que o mesmo percorra
exatamente um quadrado adiante (no sentido de seu nariz). É importante que
o hominóide passe por todos os quadrados no caminho, desde o quadrado INÍ-
CIO até o FINAL. Chega até a ser mais importante que o hominóide não bata
em qualquer parede, porque então pareceríamos idiotas, e eles não permiti-
riam que instalássemos o software no real hardware do hominóide.
Felizmente, já contamos com duas classes escritas que se encontram ar-
quivadas em nossa biblioteca. Essas classes são a Grade e a própria Hominóide.
Portanto, tudo o que vocês precisam fazer até segunda-feira é escrever o códi-
go orientado a objeto que se utiliza das operações dessas classes.
Se vocês tiverem quaisquer perguntas, podem me contatar no meu chalé
habitual no Julius Marx Country Club. Tenham um excelente fim de semana!
P.S.: Anexei breves especificações — das quais dispomos na biblioteca
para as duas classes (Hominóide e Grade).
6 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Especificações da Interface-Externa da Classe


(constante das classes na biblioteca)

Hominóide

Novo: Hominóide
// cria e retorna uma nova instância de Hominóide
virarÀEsquerda
// vira o hominóide no sentido anti-horário em 90o
virarÀDireita
// vira o hominóide no sentido horário em 90o
avançar (semPularQuadrados: NúmeroInteiro, out avançarOK: Booleano)
// move o hominóide ao longo de vários quadrados
// no sentido em que ele está apontando e retorna
// se bem-sucedido
posição: Quadrado
// retorna o atual quadrado em que se encontra o
// hominóide
apontandoParaParede: Booleano
// retorna se o hominóide estiver para bater em uma
// parede do display da grade
//mostra o hominóide como um ícone na tela

Grade

Novo: Grade
// cria e retorna uma nova instância de Grade com
// um padrão ao acaso
iniciar: Quadrado
// retorna o quadrado que é o início pensado para
// o caminho pela grade
finalizar: Quadrado
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 7

// retorna o quadrado que é o final pensado para


// o caminho pela grade
inserirHominóide (hom: Hominóide, posição: Quadrado, out inserir
OK: Booleano)
// posiciona o hominóide na grade determinada e na
// posição especificada e retorna se bem-sucedido
exibir
// mostra a grade como um padrão sobre a tela

Chave

Exemplo Significado
avançar As palavras iniciadas com letra
minúscula denotam objetos,
operações de instância e atributos
de instância.
Hominóide As palavras iniciadas com letra
maiúscula denotam classes,
operações de classe e atributos de
classe.
inserirHominóide
(hom: Hominóide, iniciarQuadrado: Quadrado,
out inserirOK: Booleano)
Denota uma operação que toma um
objeto da classe Hominóide e um
objeto da classe Quadrado, e retorna
com um objeto da classe Booleano
(sendo que o out separa os
argumentos de entrada de dados dos
argumentos de saída de dados)
:= Operador de atribuição
var inserirOK Denota uma variável de
programação inserirOK
8 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Depois de ter perdido o final de semana, a equipe surgiu com o seguinte


código orientado a objeto. No decorrer deste capítulo, muitas vezes faço refe-
rência a esse código para exemplificar as abstrações orientadas a objeto que des-
crevi.

var grade: Grade := Grade.Novo; //cria novas instâncias de


Grade...
var hom1: Hominóide := Hominóide.Novo; //... e Hominóide
// (A nova instância de Hominóide será indicada por hom1)

var inserirOK: Booleano;


var avançarOK: Booleano;
var iniciarQuadrado: Quadrado;
const umQuadrado = 1;

iniciarQuadrado := grade.iniciar;
grade.inserirHominóide (hom1, iniciarQuadrado, out inserirOK);

if not inserirOK
then aborte tudo !;
endif;

//posiciona o hominóide no sentido correto:


repeat 4 times max or until not hom1.apontandoParaParede
hom1.virarÀEsquerda;
endrepeat;

grade.exibir;
hom1.exibir;

repeat until hom1.posição = grade.finalizar

if hom1.apontandoParaParede
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 9

then hom1.virarÀEsquerda;
if hom1.apontandoParaParede
then hom1.virarÀDireita; hom1.virarÀDireita;
endif;
endif;

hom1.avançar (umQuadrado, out avançarOK);


hom1.exibir;

endrepeat
// o hominóide está no final — sucesso !

Utilizando o código do hominóide como ilustração, vamos voltar às nove


propriedades da orientação a objeto que indiquei anteriormente. E a primeira
vencedora é... a propriedade presente em todas as listas quando do “engalfi-
nhamento” dos experts: encapsulamento.

1.1 Encapsulamento

Encapsulamento é o agrupamento de idéias afins em uma unidade,


conceito esse que pode então ser informado em uma só palavra.

O encapsulamento de software é um conceito quase tão antigo quanto o pró-


prio software. No princípio da década de 40, alguns programadores notaram
que o mesmo modelo de instrução apareceria diversas vezes dentro do mesmo
programa. Alguns estudiosos (tais como Maurice Wilkes e seus colegas da
Cambridge University) logo concluíram que esse tipo de modelo repetido po-
deria ser transportado para um canto do programa, e mesmo requisitado, sob
a forma de um único nome a partir de vários pontos diferentes do programa
principal3.
Dessa forma nasceu a sub-rotina, conforme foi nomeado este encapsula-
mento de instruções. A sub-rotina era certamente uma boa maneira de econo-
mizar memória em computadores — um item muito precioso naqueles tempos.
Entretanto, as pessoas subseqüentemente compreenderam que a sub-rotina
também poupava a memória humana: ela representava certa quantidade

3 Veja: Wilkes et al., 1951.


10 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

apreciável de conceitos que determinada pessoa poderia (pelo menos, de certa


forma) considerar e manipular como uma única idéia.

Como exemplo, veja a Figura 1.2, que mostra uma sub-rotina para uma
aplicação de empréstimo.

Figura 1.2 Uma sub-rotina.

O encapsulamento em orientação a objeto tem uma finalidade similar à


da sub-rotina. Entretanto, o encapsulamento é estruturalmente mais sofisti-
cado.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 11

O encapsulamento orientado a objeto é o pacote de operações e atribu-


tos o qual representa o estado em um tipo de objeto, de tal forma
que o estado é acessível ou modificável somente pela interface provi-
da pelo encapsulamento4.

Um objeto consiste de um conjunto de operações e um de atributos, con-


forme mostrado na Figura 1.3. Por exemplo, um objeto, tal como o indicado
por hom1, apresenta as seguintes operações:

Figura 1.3 Operações e atributos de um objeto hominóide.

virarÀEsquerda, que vira o objeto hominóide para a esquerda em 90 graus,


e
avançar, que move o hominóide para frente

Cada operação é um procedimento ou uma função normalmente visível


para os outros objetos, o que significa que ela pode vir a ser requisitada pelos
outros objetos.
Os atributos representam as informações que determinado objeto evoca5. Os
atributos são acessados e atualizados somente pelas operações de um objeto. Em
outras palavras, nenhum outro objeto pode acessar um atributo tomando direta-
mente a(s) variável(eis) subjacente(s) que implementa(m) o atributo. Outro obje-

4 Em outras palavras, é o encapsulamento de estado dentro dos mecanismos de procedimentos


que acessa e modifica esse estado.
5 Você pode imaginar esses atributos contendo dados. Entretanto, como veremos mais adiante,
os atributos geralmente referem-se a outros objetos, em vez de referirem-se aos antigos e sim-
ples dados.
12 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

to, que necessite das informações retidas por um atributo, somente pode aces-
sar essas informações se recorrer a uma das operações do objeto.
Uma vez que apenas as operações do objeto podem ler e atualizar os seus
atributos, essas operações formam um anel protetor em volta do núcleo cen-
tral das variáveis implementadas no interior do objeto. Por exemplo, a opera-
ção posição (a propósito, provavelmente implementada como uma função)
ajusta os objetos situados do lado externo do hominóide com a posição do ho-
minóide (presumivelmente na forma de um par de coordenadas x, y). Nós não
podemos acessar diretamente qualquer variável implementada no interior do
objeto (tal como Pos em x e Pos em y) para obter essas informações de maneira
direta.
Uma estrutura de objeto, portanto, se parece com uma cidade européia
medieval, que tradicionalmente era cercada por uma muralha de proteção.
Portões bem-definidos e protegidos ao redor dessa muralha regulavam o in-
gresso e o egresso na cidade. Na Figura 1.4, mostro uma cidade murada cujos
portões, foram denominados segundo os nomes de operação do hominóide.

Figura 1.4 Cidade murada com portões denominados por operações de objeto.

Em um dia medieval típico, os moradores fiéis e honestos das vizinhanças


entrariam na cidade pelos portões. Eles comprariam porcos no mercado e, em
seguida, deixariam a cidade por um desses portões. Somente os aldeãos mais
canalhas, ou os malandros mais inescrupulosos, se atreveriam a escalar as
muralhas, furtar um porco e fugir pulando pelos parapeitos das fortificações.
No interesse da exatidão, eu deveria ressaltar que muitas linguagens
orientadas a objeto permitem aos programadores projetar cada atributo e ope-
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 13

ração como públicos (visíveis a outros objetos) ou privados (visíveis unicamen-


te no interior do objeto). A menos que eu estabeleça diferentemente, utilizo
operação para significar a operação típica, publicamente visível, e atributo
para significar o atributo típico, também publicamente visível.

1.2 Ocultação de Informações e Implementações


Você poderá ver uma unidade encapsulada a partir do exterior (a “visão pú-
blica”) ou do interior (a “visão privada”). O desfecho de um bom encapsula-
mento é a supressão, na visão pública, dos inumeráveis detalhes que podem
ser vistos na visão privada. Essa supressão toma duas formas: ocultação de in-
formações e ocultação de implementações.
O termo ocultação de informações implica que as informações dentro da
unidade não podem ser percebidas a partir do lado de fora da mesma. O termo
ocultação de implementações implica que os detalhes de implementação den-
tro da unidade também não podem ser percebidos desde o seu exterior.

A ocultação de informações e implementações é a utilização de encap-


sulamento para restringir a visibilidade externa de certos detalhes
de informações ou implementações, os quais são internos à estrutura
de encapsulamento.

O objeto hominóide exemplifica a propriedade de ocultação de informa-


ções visto que ele contém algumas informações privadas que são inacessíveis
a partir do lado de fora. Um exemplo é o sentido para o qual o hominóide está
apontando. A partir do lado externo do objeto, podemos alterar essa informa-
ção (para virarÀEsquerda, provavelmente), mas não podemos encontrar o seu
valor — exceto, suponho, se fizermos que o hominóide se revele a si próprio,
o que nos permitirá observar para que lado o nariz dele está apontando.
Entretanto, o termo ocultação de informações enfoca somente uma parte
do que um bom encapsulamento pode ocultar. O encapsulamento freqüente-
mente revela informações, mas oculta implementações. Essa característica é
vital à orientação a objeto. A variável no interior de um objeto que requisita
as informações fornecidas por um atributo não necessita ser implementada da
mesma forma que o atributo em si, o qual fica disponível para outros objetos.
Por exemplo, muito embora o objeto hominóide nos informe qual é a sua
posição (por meio da operação posição), não sabemos como o objeto armazena
sua posição internamente. Poderia ser como (Coord x,y) ou (Coord y,x), ou coor-
denadas polares, ou por um outro excelente esquema que algum criador con-
14 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

cebeu à 1 hora da madrugada. Contanto que o objeto exporte para nós, seus
clientes, sua posição de uma forma aceitável, não nos importa como ele se lem-
bra de sua posição.
Assim, o sentido do Hominóide é um exemplo tanto da ocultação de infor-
mações como da ocultação de implementações. Por exemplo, não sabemos se a
informação sobre o sentido é mantida dentro do objeto como um ângulo numé-
rico (com valores de 0 a 359 graus), como caracteres simples (com valores de
N, L, S e O), ou como percentualSentido, que expressa o sentido para o qual o
hominóide está voltado como uma porcentagem de um círculo pleno, de 0 a
99,999.
Em um redesenho futuro, talvez decidiríamos por revelar a informação
sobre o sentido e, assim, proporcionaríamos uma operação para exportar o
atributo do sentido para outros objetos. Mas, mesmo assim, reteríamos a ocul-
tação da implementação porque ainda não necessitaríamos saber se a imple-
mentação dentro do objeto era a mesma que a da informação pública.
Por exemplo, podemos decidir que o objeto deverá manter o sentido inter-
namente na forma de caractere e — após convertê-lo — efetuar sua exporta-
ção publicamente na forma angular. Em outras palavras, a operação que
proporciona o valor desse atributo poderia convertê-lo desde uma representa-
ção interna idiossincrática até um ângulo numérico que a maioria das pessoas
gostaria de ver como o atributo sentido.
A ocultação de informações e implementações é uma técnica poderosa
para lidar com a complexidade existente em software. Isso significa que um
objeto se parece com uma caixa preta para um observador externo. Em outras
palavras, o observador externo tem pleno conhecimento do que o objeto pode
fazer, mas não tem conhecimento de como ele pode fazer isso, ou de como ele
é construído internamente. Eu mostrarei isso esquematicamente na Figura 1.5.

Figura 1.5 O objeto Hominóide visto como uma “caixa preta”.


CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 15

A ocultação de informações e implementações apresenta dois benefícios


principais:

1. Ela localiza decisões de desenho. Decisões privadas de desenho (as que fi-
cam dentro de um objeto) têm pouco ou nenhum impacto sobre o resto do
sistema. Portanto, essas decisões locais podem ser feitas e mudadas com
um mínimo impacto sobre o sistema como um todo. Isso limita o efeito da
“pequena ondulação de mudança”.
2. Ela desacopla o conteúdo de informações da sua forma de representação.
Dessa forma, nenhum usuário de informações, que se encontra externo a
um objeto, pode tornar-se vinculado a qualquer formato particular de in-
formações internas. Isso evita que usuários externos de um objeto (por
exemplo, outros programadores) possam intervir no interior do mesmo.
Isso também evita que programadores velhacos introduzam conexões ins-
táveis em um objeto as quais dependam de artimanhas e acidentes de for-
mato. (Eu sei que você não faria uma coisa dessas, mas talvez você já
tenha encontrado por acaso os libertinos de software, aos quais me refiro.)

1.3 Retenção de Estado


A terceira abstração da orientação a objeto pertence à habilidade de um objeto
reter seu estado. Quando um módulo de procedimento tradicional (função,
subprograma, procedimento, e assim por diante) retorna ao seu chamador
(caller) sem quaisquer efeitos colaterais, o módulo “morre”, deixando como le-
gado somente o seu resultado. Quando o mesmo módulo é requisitado nova-
mente, ele parece recém-nascido. O módulo não se lembra de nada do que
aconteceu em sua existência anterior; na verdade, como os humanos, ele não
tem nem mesmo idéia de que já teve uma existência passada.
Mas um objeto, tal como o hominóide, é ciente de seu passado. Ele retém
informações dentro dele mesmo durante um período indefinido de tempo. Por
exemplo, um “caller” de um objeto pode fornecer-lhe uma informação, e esse
caller — ou outro qualquer — pode mais tarde pedir ao objeto que ele apre-
sente aquela informação novamente. Em outras palavras, um objeto não “mor-
re” quando ele termina de ser executado: ele fica a postos fielmente, pronto
para entrar em ação mais uma vez.
Tecnicamente falando, um objeto retém estado6. (Estado significa, na
prática, o conjunto de valores que um objeto consegue manter consigo. Eu dis-

6 Se você estiver familiarizado com desenho estruturado, poderá reconhecer esse conceito como
memória de estado, conforme exemplificado por um agrupamento de informações. Veja Page-
Jone, 1998.
16 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

cuto isso posteriormente no Capítulo 10.) Por exemplo, o hominóide retém in-
definidamente o conhecimento de qual é o quadrado no qual ele se encontra e
o sentido para o qual está apontando. Todavia, vimos, nas seções 1.1. e 1.2,
que a forma como o objeto escolhe para reter esse conhecimento corresponde
a uma tarefa interna, exclusiva dele.
O encapsulamento orientado a objeto, a ocultação de informações e imple-
mentações, assim como a retenção de estado, estão no centro da orientação a
objeto. Mas essas não são idéias novas. Professores acadêmicos de informática,
extremamente dedicados e trabalhadores, curvados durante incontáveis anos
em torno de quadros-negros pelo mundo inteiro, estudaram essas idéias sob o
termo abstract data-type — ADT (tipo de dado abstrato)7. Entretanto, a orien-
tação a objeto vai muito além do ADT, como as próximas seis propriedades da
orientação a objeto (nos itens 1.4 a 1.9) revelarão.

1.4 Identidade de Objeto


A primeira propriedade da orientação a objeto que transcende ao conceito do
ADT é crucial: todo objeto tem sua própria identidade.

A identidade de objeto é a propriedade pela qual cada objeto (indepen-


dentemente de sua classe ou seu estado) pode ser identificado e trata-
do como uma entidade distinta de software.

Há algo singular sobre um dado objeto que se distingue de todos os obje-


tos pertencentes à mesma classe ou grupo. Esse “algo singular” é fornecido
pelo mecanismo de identificador do objeto (object-handle)8, que explicarei dis-
secando uma linha do código do hominóide:

var hom1: Hominóide := Hominóide.Novo;

O lado direito dessa linha cria um novo objeto (da classe Hominóide), o
qual mostro na Figura 1.6. Observe o identificador do objeto, que para o objeto
da figura é o número 602237. O identificador é uma identidade anexada a um
objeto quando este é criado.

7 Um ADT é um tipo de dado (data-type) que fornece um conjunto de valores e um conjunto de


operações inter-relacionadas em que cada uma de suas definições externas (conforme visto por
usuários externos desse data-type) é independente de sua representação ou implementação in-
terna. Veja Page-Jones, 1988, como exemplo.
8 As pessoas que consideram o termo identificador de objeto muito informal utilizam, como
substituto, o termo referência de objeto.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 17

Figura 1.6 Um objeto com seu identificador.

Duas regras aplicam-se a identificadores:

1. O mesmo identificador permanece com o objeto por toda sua vida, inde-
pendentemente do que possa acontecer ao objeto durante esse período.
2. Dois objetos nunca podem ter o mesmo identificador. Sempre que o siste-
ma cria um novo objeto, com o passar do tempo o sistema designa um
identificador, diferente de todos os outros identificadores, — do passado,
do presente e do futuro9. Portanto, você sempre poderá distinguir dois ob-
jetos, mesmo se forem idênticos quanto à estrutura e informações que
eles retenham. Eles terão identificadores diferentes.
O lado esquerdo da linha do código é a notação var hom1: Hominóide. Essa
é uma notação normal de programa que fornece um nome expressivo de pro-
gramação (neste caso, hom1) para, digamos, uma palavra de memória que
pode reter um valor. Aqui, o termo Hominóide é o nome da classe do hom1, um
tópico que discutirei na seção 1.6.
Como você já deve ter adivinhado, a atribuição (:=) (que você poderá ler
como “agora aponta para” ou “agora refere-se a”), faz com que a variável hom1 re-
tenha o identificador do objeto criado no lado direito da assertiva de atribuição10.
Ninguém (programador, usuário ou qualquer outra pessoa) jamais verá
realmente o identificador do novo objeto (602237), a menos que eles se enraí-
zem pela memória com um “depurador”. Em vez disso, o programador acessa-

9 O identificador é conhecido formalmente como identificador de objeto (object identifier —


OID). A maioria dos ambientes orientados a objeto cria esse singular OID automaticamente.
Da mesma forma, a maioria dos ambientes não são tão puros como sugiro neste ponto. Por
exemplo, eles podem reciclar identificadores antigos a partir de objetos mortos, tomando-se o
cuidado para preservar a singularidade do identificador unicamente entre o agrupamento de
objetos existentes no presente.
10 Eu utilizo “aponta para” (e, mais adiante, “indicador” “pointer”) em um sentido geral. Tendo
o termo “pointer”, como base incluo pointers de C++, referências de C++, entidades de Eiffel,
variáveis de Smalltalk e Java e assim por diante.
18 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

rá o objeto por meio da variável hom1, que foi nomeada pelo programador. Em
outras palavras, hom1 aponta para o objeto cujo identificador é 602237, con-
forme mostrado na Figura 1.7.

Figura 1.7 hom1 aponta para o objeto cujo identificador é 602237.

Alguns ambientes orientados a objeto utilizam o endereço físico da memó-


ria do objeto como o seu identificador. Isso é simples, mas pode se tornar de-
sastroso se o objeto for movido na memória ou se for trocado no disco. É
melhor que o identificador seja um número ao acaso e sem sentido, mas único
(se bem que, é certo, uma vez que não somos projetistas de compiladores não
temos qualquer controle sobre como o computador faz surgir valores para
identificadores do nada).
Vamos dizer que agora devêssemos executar outra linha de código similar:

var hom2: Hominóide := Hominóide.Novo;

Essa linha cria outro objeto (também da classe Hominóide) com um iden-
tificador de, digamos, 142857, e então armazena esse identificador na variável
hom2. (Veja a Figura 1.8.)

Figura 1.8 hom2 aponta para o objeto cujo identificador é 142857.


CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 19

Para chegar ao ponto desejado, escreverei outra assertiva de atribuição:

hom2 := hom1

Agora, ambas as variáveis, hom1 e hom2, apontam para o mesmo objeto


(o primeiro deles nós criamos, a instância de Hominóide com o identificador
602237). Veja a Figura 1.9.

Figura 1.9 Agora, ambas as variáveis, hom1 e hom2, apontam para o


mesmo objeto, e o outro objeto não é mais atingível.

Ter duas variáveis apontando para o mesmo objeto não é tipicamente


útil. Mas, ainda pior, agora não temos meios de atingir o segundo objeto
(aquele cujo identificador é 142857). Efetivamente, por conseguinte, esse ob-
jeto desapareceu — exatamente como se ele tivesse caído em um buraco negro!
Na prática, ele realmente desapareceu. A maioria dos ambientes orientados a
objeto chamaria um coletor de lixo nesse momento crítico para remover o ob-
jeto da memória11.

11 Um coletor de lixo nesse contexto é um serviço do ambiente operacional, não aquele grande
e barulhento caminhão malcheiroso que passa roncando em sua rua sem saída todas as ma-
nhãs de sexta-feira. A coleta de lixo automatizada é bem-implementada em Java e Eiffel, mas
não — neste contexto — em meios que utilizam a linguagem C++.
20 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A idéia de dar a cada objeto sua própria identidade por meio de um iden-
tificador parece inócua ao extremo. Entretanto, surpreendentemente, essa
simples idéia provoca uma profunda mudança na forma como desenhamos e
construímos software orientado a objeto. E essa mudança é tratada na seção
seguinte. Continue sintonizado!

1.5 Mensagens
Um objeto solicita a outro que execute uma atividade via uma mensagem.
Muitas mensagens também transmitem algumas informações de um objeto
para outro. A maioria dos experts incluíram mensagens em suas listas de pro-
priedades vitais relativas à orientação a objeto.

Uma mensagem é o veículo pelo qual um objeto remetente obj1 trans-


mite a um objeto destinatário obj2 um pedido para o obj2 aplicar
um de seus métodos12.

Nesta seção, descrevo a anatomia de uma mensagem, as características dos


argumentos de mensagens, o papel de um objeto enviando uma mensagem, o pa-
pel de um objeto recebendo uma mensagem, e os três tipos de mensagens.

1.5.1 Estruturas de mensagens


Uma mensagem compreende diversos pedaços sintáticos, cada um dos quais é
importante por si mesmo no desenho orientado a objeto. Na verdade, retorna-
mos a cada pedaço de uma mensagem muitas vezes no decorrer do livro.
A fim de que o objeto obj1 envie uma mensagem compreensível para o
obj2, o objeto obj1 deve conhecer três coisas:

1. O identificador do obj2. (Obviamente, ao enviar uma mensagem, você de-


veria saber para quem ela está sendo enviada.) O obj1 irá normalmente
armazenar o identificador do obj2 em uma de suas variáveis.
2. O nome da operação do obj2 que o obj1 deseja executar.
3. Quaisquer informações suplementares (argumentos) que o obj2 solicitar
quando da execução de sua operação.

12 O objeto obj1 e o objeto obj2 podem ser o mesmo objeto. Por conseguinte, como discuto no
Capítulo 5, um objeto pode enviar uma mensagem a si próprio.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 21

O objeto que envia a mensagem (obj1, no exemplo anterior) é denominado


remetente, e o objeto que recebe a mensagem (obj2) é denominado destinatário13.
O software do hominóide propicia diversos exemplos de mensagens14.
Uma é a:
hom1.virarÀDireita;

Aqui, hom1 aponta para o (contém o identificador do objeto) objeto desti-


natário da mensagem. (Se você recorda, o identificador de hom1 foi atribuído
pela assertiva var hom1 := Hominóide.Novo.) virarÀDireita é o nome da operação
(pertencente ao objeto destinatário) que deverá ser executada. (Essa mensa-
gem não precisa de quaisquer argumentos: virarÀDireita sempre vira a 900.)
Enviar uma mensagem é como requisitar uma função ou um procedimen-
to tradicional. Por exemplo, em uma linguagem “pré-OO”, poderíamos ter dito:

call virarÀDireita (hom1);

Mas observe a inversão. Com as técnicas de software convencionais, re-


corremos a uma unidade de procedimento e a suprimos com o objeto sobre o
qual desejamos atuar; na orientação a objeto, recorremos a um objeto, o qual,
por conseguinte, executa uma de suas unidades de procedimento.
Neste estágio do livro, essa distinção parece apenas sintática — ou, na
melhor das hipóteses, filosófica. Entretanto, quando eu discutir polimorfismo,
sobreposição e união dinâmica na seção 1.8, veremos que essa ênfase em “ob-
jeto primeiro, procedimento depois” conduz a uma importante diferença prá-
tica entre a estrutura orientada a objeto e a estrutura convencional. Isso
porque classes diferentes de objetos podem utilizar o mesmo nome de operação
para operações que executem comportamentos diferentes, específicos de classe,
ou que executem comportamentos similares, porém através de meios diferentes.

1.5.2 Argumentos de mensagens


Semelhante à antiquada sub-rotina, a maioria das mensagens passa argu-
mentos de um lado para outro. Por exemplo, se tivéssemos feito com que a
operação denominada avanço retornasse uma baliza que sustentasse o resul-
tado do avanço, então teríamos:

13 Outros termos utilizados para remetente e destinatário são, respectivamente, cliente e servi-
dor (ou serviço).
14 O código no exemplo anterior deste capítulo é um código dentro de um objeto remetente que
não especifiquei.
22 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Desse modo, a estrutura de uma mensagem para um objeto destinatário


é definida pela assinatura da operação-alvo a ser invocada. Essa assinatura
compreende três partes: o nome da operação, a lista de argumentos de entrada
(prefixada por in) e a lista de argumentos de saída, também chamados argu-
mentos de retorno (prefixada por out). Cada uma das listas de argumentos
pode estar vazia15 . Para sintetizar, normalmente omito a palavra-chave in,
considerando-a o padrão (default).
Os argumentos de uma mensagem refletem outro contraste fundamental
entre o software orientado a objeto e o software convencional. Em um ambien-
te orientado a objeto puro (tal como em Smalltalk), os argumentos de mensa-
gens não são dados; eles são identificadores de objetos. Os argumentos de
mensagens são, portanto, como objetos vivos!
Por exemplo, a Figura 1.10a mostra hom1.avançar (semPularQuadrados,
out avançarOK), mensagem proveniente do programa do hominóide, em uma
notação gráfica informal.

Figura 1.10a A mensagem hom1.avançar (semPularQuadrados,out


avançarOK) mostrada em gráfico informal.

15 O mesmo argumento pode aparecer em ambas as listas ou somente uma vez, pré-fixado por
inout. Entretanto, esse caso é raro em orientação a objeto pura. (Veja o exercício 3, no final
deste capítulo.)
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 23

Se tirássemos um “instantâneo” do programa do hominóide em progresso,


enquanto ele estivesse executando essa mensagem, e descarregássemos os va-
lores dos argumentos da mensagem, encontraríamos algo inesperado. Nós po-
deríamos encontrar, por exemplo:

semPularQuadrados fixado em 123432


avançarOK fixado em 664730

Por que esses números estranhos? Pelo fato de que 123432 poderia ser o iden-
tificador do objeto (da classe NúmeroInteiro), o qual normalmente lembraríamos
como o número inteiro 2, e 664730 poderia ser o identificador do objeto (da classe
Booleano), que normalmente lembraríamos como o valor lógico verdadeiro.16
Incidentemente, se você preferir uma notação mais formal para a mensa-
gem anterior, a Fig.1.10b apresenta-a em uma notação de UML, que aborda
em profundidade nos Capítulos 3 a 7.

Figura 1.10b A mensagem hom1.avançar (semPularQuadrados,


out avançarOK) mostrada em UML.

Pensando em outro exemplo, se estivéssemos desenvolvendo um sistema


pessoal orientado a objeto, e fizéssemos a mesma coisa, poderíamos encontrar
o argumento funcDoMês fixado em 441523. O identificador do objeto (da classe
Funcionário), e que representa o sr. Jim Spriggs, talvez fosse o 441523.

1.5.3 Os papéis dos objetos em mensagens


Nesta seção, recapitulo os quatro papéis que vimos os objetos desempenharem
em um sistema orientado a objeto. Um objeto pode ser:

• o remetente de uma mensagem;


• o destinatário de uma mensagem;

16 Eu não estou sugerindo que o seu ambiente orientado a objeto utilizaria esses números exa-
tos. Eu os utilizo apenas a título de ilustração.
24 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• indicado por uma variável no interior de outro objeto (como vimos na


seção 1.4);
• indicado por um argumento que foi passado de um lado para outro em
uma mensagem (como vimos na seção 1.5.2).

Um dado objeto pode desempenhar um ou mais desses papéis durante


sua existência. Na Figura 1.11, podemos ver todos esses papéis coreografados
ao mesmo tempo.

Figura 1.11 Operação de um objeto enviando mensagens


para os tês objetos indicados por variáveis.

Na figura, observamos uma operação op de um objeto obj. Ela envia men-


sagens para os objetos indicados a partir de cada uma das três variáveis do
obj. A primeira mensagem era apenas um argumento de entrada, a segunda
mensagem era apenas um argumento de saída e a terceira era tanto um ar-
gumento de entrada como de saída. Cada um desses argumentos constitui, em
seus próprios termos, um indicador de um objeto. Essa estrutura é muito tí-
pica de como as operações de um objeto interagem com as variáveis de outro objeto.
Alguns autores sugerem que cada objeto é um “remetente nato” ou um
“destinatário nato”. Isso não ocorre dessa forma, conforme ilustrado na Figura
1.12.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 25

Figura 1.12 Duas mensagens entre pares de objetos.

Para a mensagem1, o obj1 é o remetente e o obj2 é o destinatário. Para a


mensagem2, o obj2 é o remetente e o obj3 é o destinatário. Por conseguinte,
vemos que, em ocasiões diferentes, o mesmo objeto pode desempenhar o papel
de remetente ou de destinatário. Os termos “remetente” e “destinatário” são,
portanto, relativos a uma dada mensagem. Eles não são propriedades fixas
dos próprios objetos.
Um ambiente orientado a objeto puro apenas contém objetos, cada um
dos quais desempenha um ou mais dos quatro papéis que vimos anteriormen-
te. Na orientação a objeto pura, não há necessidade de dados, porque os obje-
tos podem realizar todas as tarefas necessárias envolvendo dados do software.
E, em Smalltalk (uma linguagem orientada a objeto muito pura), realmente
não existem quaisquer dados! No run-time, há simplesmente objetos apontan-
do para outros objetos (via varíaveis) e comunicando-se uns com os outros pela
passagem de um lado para outro de identificadores de mais outros objetos.
Entretanto, na linguagem C++ (que é uma linguagem mista, orientada
tanto a função, quanto a dados e a objetos), os argumentos podem ser indica-
dores para qualquer coisa. Se o seu código em C++ for tão puro como em
Smalltalk, então os seus argumentos serão indicadores para objetos. Mas, se
você misturar objetos e dados em seu programa, então alguns de seus argu-
mentos poderão ser simples dados (ou indicadores para dados)17. Um comen-
tário similar aplica-se ao código de Java, muito embora a linguagem Java seja
é, de longe, muito menos improvisada e livre do que a linguagem C++.

1.5.4 Tipos de mensagem


Há três tipos de mensagem que um objeto pode receber: mensagens informa-
tivas, mensagens interrogativas e mensagens imperativas. Nesta seção, defino
brevemente cada espécie de mensagem e exemplico-as, utilizando-me mais
uma vez do hominóide. Nós voltaremos a essas mensagens bem no final do li-
vro, no Capítulo 12, quando examinaremos diferentes opções de desenho para
objetos de comunicação.

17 Veja o exercício 4, no final deste capítulo para mais detalhes sobre a distinção entre objetos
e valores de dados.
26 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A mensagem informativa é uma mensagem para um objeto, que forne-


ce a ele informações para que o mesmo se atualize. (Ela é também co-
nhecida como mensagem atualizada [update], para frente [forward]
ou de investida [push].) Essa mensagem constitui uma mensagem
“orientada no passado”, uma vez que ela informa ao objeto o que já
ocorreu em outro lugar.

Um exemplo de mensagem informativa é funcionárioSeCasou (dataDeCasa-


mento:Data). Esta mensagem informa a um objeto, representativo de um em-
pregado, que o verdadeiro funcionário se casou em certa data. Em geral, a
mensagem informativa narra a um objeto algo que ocorreu na parcela do mun-
do real representada por esse objeto.

A mensagem interrogativa é uma mensagem para um objeto, solicitan-


do que ele revele alguma informação sobre si próprio. (Ela é também
conhecida como mensagem de leitura [read], para trás [backward] ou
de recuo [pull].) Ela constitui uma mensagem “orientada no presen-
te”, uma vez que pergunta ao objeto determinada informação atual.

Um exemplo de mensagem interrogativa é hom1.posição, que solicita ao


hominóide que nos diga qual sua posição atual na grade.
Esse tipo de mensagem não altera nada; de fato, ela é normalmente uma
pergunta sobre o pedaço de mundo que o objeto destinatário representa.

A mensagem imperativa é uma mensagem para um objeto solicitando


que ele faça algo para si próprio, para outro objeto ou até mesmo
para o ambiente ao redor do sistema. (Ela é também conhecida como
mensagem de força [force] ou de ação [action].) Ela constitui uma
mensagem “orientada no futuro”, uma vez que pede ao objeto para
realizar certa ação no futuro imediato.

Um exemplo de mensagem imperativa é hom1.avançar, que faz com que


o hominóide avance. Esse tipo de mensagem muitas vezes resulta no cumpri-
mento dos objetos destinatários de alguns algoritmos significativos para exe-
cutar o que deve ser feito.
De maneira similar, imagine que pudéssemos enviar a seguinte mensa-
gem imperativa para um hominóide:

hom1.irParaPosição (quadrado: Quadrado, out viável: Booleano)


CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 27

Essa mensagem solicitaria que o hominóide se dirigisse a determinado


quadrado, contanto que isso fosse viável. (Os cálculos que o hominóide teria
de executar seriam imensos.)
Os sistemas orientados a objeto em tempo real, nos quais os objetos con-
trolam partes do hardware, freqüentemente contêm muitas mensagens impe-
rativas. Esses sistemas claramente ilustram o espírito orientado no futuro de
uma mensagem imperativa. Considere este exemplo extraído do mundo da ro-
bótica:

Robô.MãoEsquerda.irParaPosição (x, y, z: Comprimento, theta1,


theta2, theta3: Ângulo)

Essa mensagem posiciona a mão esquerda de um robô em uma dada po-


sição e orientação no espaço. O algoritmo talvez exija que a mão do robô, o
braço dele e/ou o próprio robô, se movam. Os seis argumentos representam os
seis graus de liberdade da mão, um item tridimensional no espaço.
A seguir, das mensagens passaremos a outra propriedade ainda mais in-
discutivelmente fundamental da orientação a objeto — a classe de objetos.

1.6 Classes
Recorde que no software do hominóide criamos um objeto (para representar
um hominóide) ao executar Hominóide.Novo. Hominóide, um exemplo de uma
classe, serviu como modelo a partir do qual criamos objetos hominóides (tal
como aquele com o identificador 602237). Sempre que executarmos a assertiva
Hominóide.Novo, geramos um objeto que é estruturalmente idêntico a qual-
quer outro objeto criado por essa assertiva. Por “estruturalmente idêntico”,
quero dizer que cada objeto hominóide apresenta as mesmas operações e va-
riáveis que os outros — especificamente, as operações e variáveis que o progra-
mador codificou quando ele escreveu a classe Hominóide18. Veja a Figura 1.13.

Uma classe é o estêncil a partir do qual são criados (gerados) objetos.


Cada objeto tem a mesma estrutura e comportamento da classe na
qual ele teve origem. Se o objeto obj pertence à classe C, dizemos
que “obj é uma instância de C”.

18 A propósito, durante todo este livro, pretendo que um ele indefinido signifique ele ou ela. Em
outras palavras, como eles dizem na burocracia do British Civil Service (Serviço Civil Britâ-
nico), deve ser entendido no seguinte documento que ele engloba ela.
28 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 1.13 Três objetos gerados a partir da mesma classe.

Existem duas diferenças entre objetos da mesma classe. Cada objeto tem
um identificador diferente e, a determinada hora, cada objeto provavelmente
terá um diferente estado (o que significa “valores” diferentes armazenados em
suas variáveis).
Em primeiro lugar, você deve estar confuso sobre a distinção entre uma
classe e um objeto. O modo mais simples de definir dessa distinção é lembrar
que:

• a classe é o que você desenha e programa;


• objeto é o que você cria (a partir de uma classe) no run-time19.

Os pacotes de software populares proporcionam uma analogia entre clas-


ses e objetos. Vamos imaginar que você compre um pacote de planilhas eletrô-
nicas chamado Visigoth 5.0, da Wallisoft Corp (fundada pelo próprio Wally
Soft). O pacote em si seria análogo à classe. As planilhas eletrônicas, criadas
por você a partir dele, seriam similares aos objetos. Cada uma das planilhas

19 Assim, a programação orientada a objeto deveria realmente ser denominada de programação


estruturada em classe. Todavia, não acho que esse termo seja compreensível!
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 29

eletrônicas teria toda a “maquinaria de planilhas eletrônicas” disponível nela,


como uma instância da classe Visigoth.
Em run-time, uma classe como a Hominóide poderá gerar 3, 300 ou 3.000
objetos (ou seja, instâncias do Hominóide). Portanto, uma classe se assemelha
a um estêncil: uma vez que se corta um formato em um estêncil, esse mesmo
formato pode ser obtido desde o estêncil milhares de vezes. Todos esses traços
serão idênticos entre si e, é certo, idênticos ao formato existente no estêncil
original20.
Para esclarecer isso ainda mais, vamos analisar cuidadosamente a popu-
lação de objetos gerados a partir de uma única classe. Como vimos, todos os
objetos de uma classe têm a mesma estrutura: o mesmo conjunto de operações
e atributos. Por conseguinte, cada objeto (instância) de uma classe tem sua
própria cópia do conjunto de métodos de que ele precisa para implementar as
operações e do conjunto de variáveis necessário para implementar os atribu-
tos21. Existem, a princípio, em determinado período de tempo, tantas cópias
dos métodos e variáveis (0, 3, 300...) quantas forem os objetos gerados nesse
determinado tempo. Veja a Figura 1.14.

Figura 1.14 Métodos, variáveis e identificadores para três objetos da mesma


classe, juntamente com os requisitos de memória para os objetos.

20 A analogia padrão da escola “Os Objetos São Apetitosos” é a de que uma classe é como um
cortador de biscoitos e os objetos são como biscoitos. Presumivelmente, o coletor de lixo é mui-
to mais parecido com um “monstro de biscoitos”.
21 Um método é a implementação de uma operação. Em termos de programação, você pode pen-
sar em um método como o código de um corpo de um procedimento (ou de uma função). De
forma similar, uma variável é a implementação de um atributo, e um identificador é a imple-
mentação de um identificador de objeto.
30 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Por um momento, se você não se importar, descerei fundo na implemen-


tação de computadores a fim de explicar mais a real estrutura de um conjunto
de objetos da mesma classe, a qual denominarei de C. Vamos supor que cada
método, ao implementar uma das operações da Figura 1.14, ocupe 6 bytes.
Portanto, o objeto1 ocupará 416 bytes de memória (ou seja, 4 × 100 + 5 × 2 +
6). Os três objetos juntos vão, portanto, ocupar 1.248 bytes (ou seja, 3 × 416).
Quinze desses mesmos objetos ocupariam 6.240 bytes (ou seja, 15 × 416).
Mas essa abordagem de alocar memória para objetos seria muito perdu-
lária, pelo fato de que cada um dos 15 conjuntos de métodos dos 15 objetos são
idênticos. E, uma vez que cada conjunto de métodos contém somente código
de procedimento, um único conjunto poderá ser compartilhado por todos os ob-
jetos. Portanto, muito embora em princípio cada objeto tenha o seu próprio
conjunto de métodos operacionais, na prática (para economizar espaço) todos
eles compartilham da mesma cópia física.
Por outro lado, muito embora os identificadores e as variáveis de cada ob-
jeto são idênticos em estrutura, de objeto a objeto, eles não podem ser com-
partilhados entre objetos. A razão certamente é que eles devem conter valores
diferentes no run-time.
Portanto, desde que todos os objetos de C compartilhem o mesmo conjunto
de operações, a memória total consumida pelos 15 objetos de C será na verda-
de de apenas 640 bytes (400 bytes para o conjunto único de métodos, 150 bytes
para os 15 conjuntos de variáveis e 90 bytes para os 15 identificadores). Essa
somatória de 640 bytes é muito melhor do que os 6.240 bytes, e constitui o
modo normal de um ambiente orientado a objeto alocar memória para objetos.
Veja a Figura 1.15.
Praticamente todas as operações e atributos que analisamos neste capí-
tulo pertencem a objetos individuais. Eles são chamados de operações de ins-
tância do objeto e atributos de instância do objeto, ou, abreviadamente,
operações de instância e atributos de instância. Entretanto, existem também
operações de classe e atributos de classe. Por definição, há exatamente um con-
junto de operações de classe e de atributos de classe para uma dada classe a
toda hora — independentemente de quantos objetos dessa classe possam ter
sido gerados.
As operações e os atributos de classe são necessários para enfrentar si-
tuações que não podem ser de responsabilidade de qualquer objeto individual.
O exemplo mais famoso de uma operação de classe é Novo, que gera um novo
objeto de determinada classe.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 31

Figura 1.15 Representação esquemática da memória real (640 bytes)


ocupada por 15 objetos da mesma classe.

A mensagem Novo nunca poderia ser enviada a um objeto individual. Di-


gamos, por exemplo, que tivéssemos três objetos da classe ClienteDoBanco, re-
presentando os reais clientes de um banco (vamos indicar esses objetos como
bob, carol e ted), e que desejássemos gerar um novo objeto para ClienteDoBanco
(digamos, alice). Para qual objeto enviaríamos a mensagem Novo? Não haveria
qualquer motivo em particular para enviá-la a bob, ao lado de carol ou ted. Ainda
pior, nunca poderíamos ter gerado o primeiro cliente do banco, porque inicial-
mente não haveria objeto da classe ClienteDoBanco a quem enviar a mensagem
Novo.Portanto, Novo é uma mensagem que deve ser enviada para uma classe,
em vez de para um objeto individual. O exemplo do jogo do hominóide era Ho-
minóide.Novo. Essa era uma mensagem de classe para a classe Hominóide exe-
cutar sua operação de classe, Novo, e dessa forma criar um objeto — uma nova
instância da classe Hominóide.
Um exemplo de um atributo de classe poderia ser semPularHominóides-
Criados: NúmeroInteiro. Esse seria incrementado por Novo cada vez que o Novo
fosse executado. Entretanto, por mais objetos hominóides que houvesse, exis-
tiria só uma cópia desse atributo de classe. Você poderia desenhar uma ope-
ração de classe para prover ao mundo exterior acesso a esse atributo de classe.
A Figura 1.16 mostra a estrutura de memória se a classe C tivesse duas
operações de classe (cada um de seus métodos ocupando 100 bytes) e três atri-
butos de classe (cada uma de suas variáveis ocupando 2 bytes). O número de
bytes para a “maquinaria de classe” (206, neste exemplo) permaneceria cons-
32 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

tante, independentemente do número de objetos que C teria gerado. Com o


acréscimo dessa maquinaria de classe, C e seu conjunto de 15 objetos consu-
miriam agora um total de 846 (ou seja, 206 + 640) bytes de memória.

Figura 1.16 Representação esquemática da memória real (846 bytes)


ocupada por 5 objetos e pela “maquinaria de classe”.

Observe que, tanto em princípio como na prática, há apenas um conjunto


de métodos de classe por classe. Isso contrasta com os métodos de instância,
nos quais, em princípio, cada objeto tem seu próprio conjunto. (Apenas para
poupar memória, realmente fazemos com que os objetos compartilhem o mes-
mo conjunto de métodos em suas operações.) A distinção entre variáveis de
classe e variáveis de instância é clara: cada classe tem somente um conjunto
de variáveis de classe, ao passo que há um conjunto de variáveis de instância
para cada objeto dessa classe, tanto em princípio como de fato.
Se você já teve a oportunidade de estudar abstract data-types (ADTs),
provavelmente na faculdade, pode estar se perguntando qual é a diferença en-
tre uma classe e um ADT. A resposta é que um ADT descreve uma interface.
É uma fachada que declara o que será suprido aos usuários do ADT, mas não
informa nada sobre como esse ADT será implementado. Uma classe é algo de
carne e osso, real (ou, pelo menos, de desenho e código internos), que imple-
menta um ADT. De fato, para um dado ADT, você poderia desenhar e cons-
truir diversas classes diferentes. Por exemplo, uma dessas classes poderia
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 33

gerar objetos que operam muito eficientemente, enquanto outra classe para o
mesmo ADT poderia produzir objetos que absorvem pouca memória.
Eu escrevo muito mais sobre abstract data-types, classes e as diferenças
entre eles na Parte III do livro. Até então, entretanto, tratarei classe e ADT
como sinônimos. Com isso em mente, vamos nos mover para o importante con-
ceito de herança.

1.7 Herança
O que você faria se tivesse criado uma classe C e então, mais tarde, descobris-
se uma classe D, praticamente idêntica à C, salvo por alguns atributos ou ope-
rações extras? Uma solução seria simplesmente duplicar todos os atributos e
operações de C, e colocá-los em D. Mas isso não só representaria um trabalho
extra para você, como a duplicação também transformaria a manutenção em
um grande aborrecimento. Uma solução melhor seria conseguir que a classe
D, de alguma forma, tivesse de “solicitar para utilizar as operações” da classe
C. Essa solução é chamada de herança.

A herança (de D a partir de C) é a habilidade que uma classe D tem


implicitamente definida em cada um dos atributos e operações da
classe C, como se esses atributos e operações tivessem sido definidos
com base na própria classe D.
C é caracterizada como uma superclasse de D. Em contrapartida, D é
caracterizada como uma subclasse de C.

Em outras palavras, por meio da herança, os objetos da classe D podem


utilizar os atributos e operações que iriam, de outra forma, somente estar dis-
poníveis para os objetos da classe C.
A herança representa outro caminho muito importante no qual a orien-
tação a objeto diverge das abordagens dos sistemas convencionais. Ela efeti-
vamente permite que você construa de forma incrementada, software, de
acordo com as instruções a seguir:

• Primeiro, construa classes para lidar com o caso mais geral.


• Em seguida, a fim de tratar com os casos especiais, acrescente classes
mais especializadas — herdadas da primeira classe. Essas novas clas-
ses estarão habilitadas à utilização de todas as operações e atributos
(tanto operações e atributos de classe, como operações e atributos de
instância) da classe original.
34 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Um exemplo talvez ajude a ilustrar o princípio. Digamos que nós temos


uma classe Aeronave em uma aplicação voltada à aviação. Aeronave pode ter
definido sobre ela uma operação de instância denominada desviar, e, no inte-
rior desta, um atributo de instância denominado rota.
A classe Aeronave lida com atividades ou informações pertinentes a qual-
quer tipo de aeronave para vôos. Entretanto, existem certos tipos especiais de
aeronaves que executam atividades especiais e, portanto, requerem uma infor-
mação exclusiva. Por exemplo, um planador realiza atividades especiais
(quando solta o seu cabo de reboque) e pode precisar fazer registros de infor-
mações especiais (por exemplo, se ele estiver acoplado a um cabo de reboque).
Por conseguinte, podemos definir outra classe, Planador, que é herdada de
Aeronave. Planador terá uma operação de instância denominada soltarCaboDe-
Reboque e um atributo de instância denominado seCaboDeReboqueAcoplado
(da classe Booleano). Isso nos fornecerá a estrutura mostrada na Figura 1.17,
na qual a seta com ponta denota herança.

Figura 1.17 Planador é uma subclasse herdada


de sua superclasse, Aeronave.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 35

Agora, analisaremos a mecânica da herança, imaginando que certo código


orientado a objeto inicialmente gera objetos das classes Aeronave e Planador, e
em seguida envia mensagens para esses objetos. O código é seguido por uma
discussão das quatro assertivas enumeradas de (1) a (4):

var ae: Aeronave := Aeronave.Novo;


var pl: Planador := Planador.Novo;
...
ae.desviar (novaRota, out desviarOK); (1)
pl.soltarCaboDeReboque; (2)
pl.desviar (novaRota, out desviarOK); (3)
ae.soltarCaboDeReboque; (4)
...

(1) O objeto indicado por ae recebe a mensagem desviar (novaRota, out


desviarOK), que faz com que ele aplique a operação desviar (com os ar-
gumentos apropriados). Uma vez que ae é uma instância de Aerona-
ve, o objeto ae irá simplesmente utilizar a operação desviar que tinha
sido definida na classe Aeronave.
(2) O objeto indicado por pl recebe a mensagem soltarCaboDeReboque,
que faz com que ele aplique a operação soltarCaboDeReboque (que
não precisa de nenhum argumento). Uma vez que pl é uma instância
de Planador, o objeto pl irá simplesmente utilizar a operação soltar-
CaboDeReboque definida na classe Planador.
(3) O objeto indicado por pl recebe a mensagem desviar (novaRota, out
desviarOK), que faz com que ele aplique a operação desviar (com os ar-
gumentos apropriados). Sem herança, essa mensagem causaria um
erro de run-time (tal como Operação Indefinida — desviar), pelo fato
de pl constituir uma instância de Planador, que não apresenta qual-
quer operação denominada desviar.
Entretanto, desde que Aeronave é uma superclasse de Planador, o ob-
jeto pl também está habilitado a utilizar qualquer operação de Aero-
nave. (Se Aeronave tivesse uma superclasse ObjetoVoador, o objeto pl
também estaria habilitado a utilizar qualquer operação de Objeto-
Voador.) Portanto, a linha de código marcada como (3) funcionará
com sucesso, e a operação desviar, conforme definida em Aeronave,
será executada.
(4) Isso não funcionará! O objeto ae refere-se a uma instância de Air-
craft, que não tem qualquer operação denominada soltarCaboDeRebo-
36 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

que. A herança não ajuda em nada neste caso, uma vez que Planador
é a única classe que tem soltarCaboDeReboque nela definida, e Plana-
dor é uma subclasse de Aeronave. Como a herança não funciona nes-
sa direção, o sistema parará com um erro de run-time. Isso parece
lógico, uma vez que o objeto ae poderia indicar um grande avião a
jato, para o qual a operação soltarCabodeReboque não teria qualquer
sentido.
Na seção 1.6, vimos a distinção entre classe e objeto. Agora, vemos que
há também uma sutil distinção entre objeto e instância. Embora até o momen-
to tenhamos utilizado objeto e instância praticamente como sinônimos um do
outro, notamos que a herança, em certo sentido, permite que um único objeto
seja simultaneamente uma instância de mais de uma classe.
Isso corresponde muito bem ao mundo real. Se você possuir um planador,
você possui exatamente um objeto com uma identificação (determinador) em
sua cauda. Ainda que este planador seja (obviamente!) um exemplo de plana-
dor e, ao mesmo tempo, um exemplo de aeronave. Por conseguinte, conceitual-
mente, o objeto que representa o que você possui é uma instância de Planador
e uma instância de Aeronave.
De fato, esse exemplo comprova um teste revelador para a utilização efi-
caz da herança: ele é chamado do teste do é. Se você puder dizer: “uma classe
D é uma classe C”, então D, quase certamente, deve ser uma subclasse de C.
Assim, já que podemos dizer “um planador é uma aeronave”, a classe Planador
deverá ser uma subclasse de Aeronave22.
Vamos explorar esse tópico mais um pouco analisando os “bastidores” da
herança. O objeto referido como pl será representado em run-time por um
amálgama de duas partes. Uma parte será constituída pelas operações e pelos
atributos de instância definidos por Planador; a outra será constituída pelas
operações e pelos atributos de instância definidos por Aeronave; conforme mos-
trado na Fig. 1.18.23
Na maioria das linguagens, a subclasse sucessora herda tudo o que a su-
perclasse tem a oferecer. A subclasse não chega a apanhar e escolher o que
ela herda. Entretanto, existem algumas artimanhas com as quais a subclasse
pode suprimir (ou seja, neutralizar) certas operações herdadas; conforme dis-
cuto na seção 1.8.

22 Eu vou explorar com mais detalhes o é como um tópico e os usos corretos da herança nos Ca-
pítulos 10, 11 e 12.
23 Na realidade, uma ferramenta denominada achatador de classe (class-flattener) — construída
em ambientes tais como Eiffel — fornece exatamente essa visão.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 37

Figura 1.18 Operações e atributos de instância disponíveis


para um objeto da classe Planador.

O código efetivo para implementar heranças em boas linguagens orienta-


das a objeto é fácil de ser compreendido. Você simplesmente enuncia a super-
classe na definição de classe de cada subclasse que deverá ser herdada dessa
superclasse. Por exemplo:
classe Planador herda de Aeronave;
...

O exemplo de herança nesta seção é o de uma herança simples, a qual in-


dica que cada classe tem, quando muito, uma superclasse direta. A herança
múltipla também é possível. Com a herança múltipla, cada classe pode ter um
número arbitrário de superclasses diretas.
A herança múltipla converte a árvore genealógica de herança simples em
uma disposição de heranças. Veja a Figura 1.19.
A herança múltipla introduz algumas difíceis questões de desenho, in-
cluindo a possibilidade de uma subclasse herdar operações (ou atributos) con-
flitantes a partir de seus múltiplos ancestrais. (Operações conflitantes têm o
mesmo nome, e a subclasse sucessora não consegue diferenciar facilmente
qual delas ela deveria herdar).
Certas dificuldades, tais como a do conflito de nomes, deram à herança
múltipla uma má reputação. Com o passar dos anos, tanto os críticos quanto
os defensores da herança múltipla atingiram um grande nível de exaltação.
Eu deveria revelar que sou a favor da herança múltipla, porque o mundo real
freqüentemente necessita da propagação de subclasses sucessoras. Como ob-
servamos na Figura 1.19, a classe AeronaveDePassageiros poderia muito bem
ser herdeira das classes Aeronave e VeículoDePassageiros.
38 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 1.19 Herança múltipla — uma subclasse com


duas ou mais superclasses.

Todavia, já que a herança múltipla pode criar estruturas complexas e in-


compreensíveis, é melhor utilizá-la judiciosamente — até mesmo mais judicio-
samente do que a herança simples.24. À época em que este livro foi escrito,
duas das mais importantes linguagens (C++ e Eiffel) apoiavam a herança
múltipla, enquanto outras duas (Java e Smalltalk) eram contrárias a ela.

1.8. Polimorfismo
A palavra polimorfismo origina-se de duas palavras gregas que significam,
respectivamente, muitas e forma. Algo que é polimórfico, portanto, apresenta
a propriedade de assumir muitas formas, como no episódio do Red Dwarf (de-
nominado muito apropriadamente Polymorph [Polimorfo]), no qual a tripula-
ção de uma nave espacial era continuamente atacada por um ser
extra-terrestre que conseguia rapidamente mudar de uma forma corporal
para outra.
Os livros didáticos contêm duas definições de polimorfismo, nenhuma de-
las tão dramática como o Red Dwarf. Na coluna de definições a seguir, eu as
marquei como (A) e (B). Ambas as definições são válidas e ambas as proprie-

24 Voltarei à questão de herança múltipla diversas vezes no livro, especialmente nos Capítulos
11, 12 e 13. É provável que você queira consultar Meyer, 1992 para uma discussão da herança
múltipla e sua prima menos útil, a herança repetida (na qual uma classe herda característi-
cas da mesma superclasse mais do que uma vez).
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 39

dades do polimorfismo trabalham juntas no intuito de trazer uma grande


quantidade de expressão à orientação a objeto. No decorrer desta seção, expli-
co essas duas definições com mais detalhes.

(A) Polimorfismo é a habilidade pela qual uma única operação ou


nome de atributo pode ser definido em mais de uma classe e assumir
implementações diferentes em cada uma dessas classes.
(B) Polimorfismo é a propriedade por meio da qual um atributo ou va-
riável pode apontar para (ou manter o identificador de) objetos de
diferentes classes em horas diferentes.

Suponha que tenhamos uma classe Polígono, que represente o tipo de for-
mato em duas dimensões que a Figura 1.20 ilustra:

Figura 1.20 Alguns polígonos planos, mas não simples.

Nós poderíamos definir uma operação denominada calcularÁrea na classe


Polígono, que resultaria no valor da área do objeto Polígono. (Note que área é
um atributo definido em Polígono e, via herança, nas subclasses de Polígono.)
A operação calcularÁrea necessitaria de um algoritmo bastante sofisticado por-
que teria de dar conta dos formatos peculiares de todos os polígonos da Figura
1.20.
Agora, vamos acrescentar mais classes — digamos Triângulo, Retângulo e
Hexágono —, as quais são subclasses (e, portanto, herdeiras) de Polígono. Isso
faz sentido, porque um triângulo é um polígono, um retângulo é um polígono,
e assim por diante. Veja a Figura 1.21.
Observe que na Figura 1.21 as classes Triângulo e Retângulo também têm
operações denominadas calcularÁrea. Essas operações realizam a mesma tare-
fa que a versão calcularÁrea da classe Polígono: calculam a área da superfície
total limitada pelo contorno.
40 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 1.21 Polígono e suas três subclasses.

Entretanto, o desenhista/programador do código responsável por imple-


mentar calcularÁrea para o Retângulo escreveria o código de forma muito dife-
rente da forma do código de calcularÁrea para o Polígono. Por quê? Devido ao
fato de que a área de um retângulo é simples (comprimento × largura), o código
para a operação calcularÁrea do Retângulo é, conseqüentemente, simples e efi-
ciente. Entretanto, uma vez que o algoritmo para calcular a área de um polí-
gono complexo arbitrário é complicado e menos eficiente, não desejaríamos
utilizá-lo para calcular a área de um retângulo.
Assim, se escrevermos um código que envie a seguinte mensagem para
um objeto referido como Formato2-D:

Formato2-D.calcularÁrea;

possivelmente não saberemos que algoritmo para cálculo de áreas será


empregado. A razão é que não podemos saber exatamente à qual classe For-
mato2-D pertence. Existem cinco possibilidades:

1. Formato2-D é uma instância de Triângulo. A operação calcularÁrea, confor-


me definida para Triângulo, será realizada.
2. Formato2-D é uma instância de Retângulo. A operação calcularÁrea, confor-
me definida para Retângulo, será realizada.
3. Formato2-D é uma instância de Hexágono. Uma vez que falta a Hexágono
uma operação denominada calcularÁrea, a operação calcularÁrea, obtida
pela herança e conforme definida para Polígono, será realizada.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 41

4. Formato2-D é uma instância de um Polígono geral, de formato arbitrário.


A operação calcularÁrea, conforme definida para Polígono, será realizada.
5. Formato2-D é uma instância de uma classe C (tal como Cliente), que não
corresponde a quaisquer das classes anteriores. Uma vez que C provavel-
mente não tem uma operação denominada calcularÁrea definida nela, o
envio da mensagem calcularÁrea resultará em um erro de compile-time ou
run-time. Isso é razoável, naturalmente, porque algo chamado Formato2-
D não deveria estar referindo-se a um cliente.

Você talvez esteja pensando em como é estranho que um objeto não possa
saber a exata classe do objeto destinatário para o qual ele está enviando uma
mensagem. Entretanto, essa situação é bastante comum. Por exemplo, na li-
nha final de código a seguir, em compile-time, não podemos dizer para qual
classe de objeto p apontará em run-time. O verdadeiro objeto a ser apontado,
será determinado pela escolha do usuário na última hora (testado pela afir-
mação if).

var p: Polígono;
var t: Triângulo := Triângulo.Novo;
var h: Hexágono := Hexágono.Novo;
...
if usuário diz OK
then p:= t
else p:=h
endif;
...
p.calcularÁrea; // aqui p pode referir-se a um objeto Triângulo ou a
um objeto Hexágono
...

Observe que, na linha do código orientado a objeto anterior, não preci-


samos de um teste junto a p.calcularÁrea para determinar que linha de calcu-
larÁrea executar. Esse é um exemplo de uma ocultação de implementação
muito oportuna. Permite-nos acrescentar uma nova subclasse da classe Polí-
gono (digamos, Octágono) sem mudar de maneira alguma o código anterior.
De forma metafórica, o objeto destinatário “sabe como fornecer a área dele”, e
assim o remetente não deve se preocupar com isso.
42 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Repare também na declaração var p: Polígono. Essa é uma restrição se-


gura aplicada no polimorfismo da variável, p. Na sintaxe de programação que
uso aqui, ela significa que ao p é permitido indicar só objetos da classe Polígo-
no (ou objetos de uma das subclasses de Polígono). Se alguma vez fosse atri-
buído a p o identificador de um objeto Cliente, ou de um objeto Cavalo,
conseqüentemente o programa pararia com um erro de run-time.
A operação calcularÁrea, sendo definida em diversas classes, proporciona
um bom exemplo de polimorfismo, conforme definido em (A). A variável p, sen-
do capaz de indicar objetos de diversas classes diferentes (por exemplo, Triân-
gulo e Hexágono), é um bom exemplo da definição em (B). O exemplo como um
todo mostra como os dois aspectos do polimorfismo trabalham em conjunto
para simplificar a programação.
Um ambiente orientado a objeto geralmente implementa o polimorfismo
por meio da ligação dinâmica (dynamic binding). O ambiente verifica a real
classe do objeto destinatário de uma mensagem no último momento possível
— no run-time, quando a mensagem é enviada.

Dynamic binding (ligação de run-time ou ligação tardia) é a técnica


pela qual a exata linha de código a ser executada é determinada so-
mente no run-time (diferentemente do que ocorre no compile-time).

O exemplo anterior, em que a operação calcularÁrea é definida em Polígo-


no e em Triângulo, também demonstra o conceito de redefinição (overriding).

Overriding é a redefinição de um método, definido em uma classe C,


em uma das subclasses de C.

A operação calcularÁrea, originalmente definida em Polígono, é redefinida


em Triângulo. A operação do Triângulo tem o mesmo nome, mas um algoritmo
diferente.
Você poderá ocasionalmente utilizar a técnica de redefinição para cance-
lar uma operação, definida em uma classe C, em uma das subclasses de C.
Você poderá cancelar uma operação redefinindo-a simplesmente para reverter
um erro. Se confia muito em cancelamento, entretanto, é porque provavelmen-
te você começou com uma hierarquia instável que envolve superclasses e sub-
classes.
Aparentado com o polimorfismo está o conceito de sobreposição (overloading),
que não deve ser confundido com redefinição (overriding).
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 43

Overloading de um nome ou símbolo ocorre quando diversas opera-


ções (ou operadores), definidos na mesma classe, têm esse nome ou
símbolo. Dizemos então que esse nome ou símbolo está sobreposto.

Ambos, o polimorfismo e a sobreposição, freqüentemente requerem que a


operação específica a ser executada seja selecionada em run-time. Conforme
vimos no pequeno exemplo de código anterior, a razão disso é que a exata clas-
se de um objeto destinatário — e, portanto, a implementação específica da
operação a ser executada — provavelmente não será conhecida até o run-time.
A distinção normal entre polimorfismo e sobreposição é que o polimorfis-
mo permite que o mesmo nome de operação seja definido diferentemente pelas
diferentes classes, enquanto que a sobreposição permite que o mesmo nome de
operação seja definido diferentemente diversas vezes dentro da mesma clas-
se25.
A escolha da operação polimórfica será feita dependendo unicamente da
classe do objeto destinatário a quem a mensagem é destinada. Porém, tendo-
se uma operação sobreposta, como a correta linha de código fica vinculada ao
nome de operação em run-time? A resposta é: pela assinatura — o número
e/ou classe dos argumentos — da mensagem. Aqui constam dois exemplos:
1a. produto1.rebaixarPreço
1b. produto1.rebaixarPreço (grandePorcentagem)
2a. matriz1 × i
2b. matriz1 × matriz2
No primeiro exemplo, o preço de um produto é reduzido pela operação re-
baixarPreço. Se rebaixarPreço for invocada com zero argumentos (como em 1a),
conseqüentemente a operação utiliza uma porcentagem padrão de desconto; se
rebaixarPreço for invocada com um argumento (o argumento de grandePorcen-
tagem de 1b), por conseguinte a operação aplica o valor estipulado de grande-
Porcentagem.
No segundo exemplo, o operador de multiplicação (×) está sobreposto. Se
o segundo operando for um número inteiro (como em 2a), conseqüentemente
o operador (×) será uma multiplicação escalar. Se o segundo operando for ou-
tra matriz (como em 2b), por conseguinte o operador (×) será uma multiplica-
ção entre matrizes.

25 Ou, para ser mais geral, dentro do mesmo escopo-nome.


44 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

1.9 Generalização

Generalização é a construção de uma classe C de forma que uma ou


mais das classes que ela utiliza internamente é fornecida somente
em run-time (na hora em que um objeto da classe C é gerado).

A melhor forma de ilustrar o conceito de generalização é por meio de uma his-


tória ocorrida nos empolgantes anos do passado. Quando eu era estudante
universitário, fiz um curso denominado Estruturas de Dados 101. Em certo se-
mestre, o professor Rossini deu-nos a tarefa de desenhar e programar uma ár-
vore binária balanceada e classificada de números inteiros. (Veja a Figura
1.22.) A principal característica de uma árvore balanceada é que todas as suas
folhas “atingem o patamar mais baixo” no mesmo nível (dão ou tomam um ní-
vel).

Figura 1.22 Árvore binária balanceada de números


inteiros escolhidos ao acaso.

Tudo é muito fácil de ser compreendido — até que você tenha de inserir
outro número inteiro na árvore (digamos 5, conforme mostrado na Figura
1.23). Em conseqüência, a árvore pode perder o balanceamento e pode ser pre-
ciso executar um árduo rearranjo em sua estrutura até que ela recupere o ba-
lanço.
Após muitas horas de planejamento no computador e de depuração (de-
bugging) on-line, a maioria de nós colocou nossos algoritmos para funcionar.
Com sorrisos de auto-satisfação, apresentamos nossos programas, saímos em
férias, e fizemos repetidos esforços noturnos para esquecer tudo sobre árvores
binárias balanceadas e classificadas de números inteiros.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 45

Figura 1.23 Uma árvore que acabou de ficar sem o balanceamento.

Entretanto, infelizmente, o exercício da árvore balanceada do professor


Rossini era só uma introdução para uma tarefa muito maior. Como parte de
uma aplicação durante o segundo semestre (no curso Aplicações Voltadas a
Negócios 101), precisamos manter listas selecionadas de clientes e produtos.
Aqueles da nossa turma que fizeram o curso Estruturas de Dados 1 e que não
tinham conseguido apagar totalmente sua lembrança das árvores binárias ba-
lanceadas e classificadas, simplesmente “tiraram da gaveta” nosso antigo có-
digo e o copiaram duas vezes. Em uma cópia, substituímos NúmeroInteiro por
IdentificaçãoDoCliente; na outra, substituímos NúmeroInteiro por Identificação-
DoProduto.
Essa clonagem do código antigo aumentou grandemente nossa produtivi-
dade. Entretanto, essa abordagem não constitui um “símbolo de prata”, devido
ao fato de existir um risco significativo de clonagem. O risco surge do fato de
que agora temos de manter três cópias de um código praticamente idêntico.
Por conseguinte, se descobríssemos um melhor algoritmo para o balan-
ceamento da árvore, teríamos de reexaminar três versões de código. Isso não
só representaria trabalho extra, como gerenciar as três versões também seria
algo complicado (a menos que pudéssemos aparecer com um alterador [chan-
ger] de clones automático — e pronto). O que precisaríamos era de um modo
de escrever a estrutura básica do algoritmo para balanceamento da árvore
apenas uma vez (e não simplesmente cloná-lo) para que pudéssemos aplicá-lo
tantas vezes o quanto desejássemos em números inteiros, clientes, produtos
ou em qualquer outra coisa.
Nesse ponto, a generalização chega galopando montada em um cavalo
branco para nos salvar. Se definirmos ÁrvoreBalanceada como uma classe pa-
rametrizada (conhecida anteriormente como uma classe genérica), isso signi-
46 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

ficará que, pelo menos, uma das classes usadas dentro de ÁrvoreBalanceada
não necessitaria ser atribuída até o run-time26. Esta presumivelmente é a
classe dos itens a serem armazenados nos “nós”27 do objeto da particular ár-
vore balanceada gerada por nós.
Portanto, poderia escrever a classe ÁrvoreBalanceada como segue:

Classe ÁrvoreBalanceada <ClasseDoItemDoNó>;


...
var atualNó: ClasseDoItemDoNó := ClasseDoItemDoNó.Nova;
...
atualNó.imprimir;
...

Repare no argumento parametrizado da classe ClasseDoItemDoNó. Esse é


um argumento formal, cujo real “valor” será fornecido somente em run-time.
Por exemplo, quando geramos um novo objeto da classe ÁrvoreBalanceada, es-
tamos fornecendo um nome de classe real como argumento, conforme segue:

...
var custoÁrvore: ÁrvoreBalanceada := ÁrvoreBalanceada.Nova
<Cliente>;
var produçãoÁrvore: ÁrvoreBalanceada := ÁrvoreBalanceada.Nova
<Produto>;
...

Dessa forma, custoÁrvore agora refere-se a um objeto (uma instância de


ÁrvoreBalanceada) que retém instâncias da classe Cliente em seus nós, confor-
me mostrado na Figura 1.24. (De forma similar ocorre para produçãoÁrvore,
naturalmente.)
É como se tivéssemos clonado a primeira linha do código duas vezes (uma
para Cliente, outra para Produto), para ler:

classe ÁrvoreBalanceadaCliente;
...
var atualNó: Cliente:= Cliente.Novo;

26 Uma classe parametrizada é conhecida em C++ como uma classe modelo (template class).
27 N.T.: Nó significa “parte central”.
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 47

Figura 1.24 Árvore binária balanceada de objetos Cliente. (Eu represento


os clientes mantidos nos nós por seus primeiros nomes.)
Esta árvore é o objeto a que se refere a variável custoÁrvore.

...
atualNó.imprimir;
...
classe ÁrvoreBalanceadaProduto;
...
var atualNó: Produto:= Produto.Novo;
...
atualNó.imprimir;
...

Finalmente, observe a assertiva atualNó.imprimir. Ela corresponde a um


belo exemplo de polimorfismo, porque, quando a escrevemos na classe para-
metrizada ÁrvoreBalanceada, não sabemos que classe de atualNó poderia ser.
Dessa forma, qualquer pessoa que estiver gerando uma classe ÁrvoreBalancea-
da terá de tomar muito cuidado, pois a operação imprimir, na verdade, é defi-
nida em qualquer classe utilizada para gerar uma árvore específica.
Outro exemplo: se desenhar uma classe parametrizada MesaDeCorte
<C>, você deverá salientar que qualquer classe (tal como Símbolo), especifica-
da como o real argumento de C, terá obrigatoriamente a operação corte defi-
nida nela. (Eu trato em detalhes desse possível perigo de generalização no
Capítulo 12.)
Você talvez tenha concluído que há uma maneira de escrever ÁrvoreBa-
lanceada sem generalização e sem clonagem. Poderíamos permitir que os nós
de uma árvore balanceada aceitassem um objeto da classe mais elevada na
48 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

hierarquia de superclasses e subclasses. Se denominamos essa classe mais


alta como Objeto, então o código pode ser:

classe ÁrvoreBalanceada;
...
var atualNó: Objeto := Objeto.Novo;
...
atualNó.imprimir;
...

Agora, cada nó de uma árvore balanceada aceitaria a inserção de absolu-


tamente qualquer espécie de objeto. Em particular, poderíamos misturar
clientes, números inteiros, produtos, polígonos, cowboys e cavalos, na mesmís-
sima árvore. Isso decerto constituiria uma tolice. Ainda pior, seria muito im-
provável que todas essas diferentes classes de objetos chegassem a entender
a mensagem imprimir.
Ambas as classes, ÁrvoreBalanceada e MesaDeCorte, são exemplos de clas-
ses contêiner. Uma classe contêiner serve para manter objetos em um tipo de
estrutura (freqüentemente sofisticada). A generalização é muitas vezes utili-
zada em orientação a objeto para desenhar essas classes contêiner. Muito em-
bora a generalização não seja estritamente necessária para escrever código
reutilizável para classes contêiner, ela é certamente superior ao código clonado
ou a qualquer desenho frágil, em que os objetos das classes arbitrárias são
misturados dentro do mesmo recipiente (contêiner).

1.10 Resumo
Uma vez que o termo orientação a objeto se ressente a priori da falta de um
significado em nosso idioma, tem havido muito pouco consenso histórico sobre
o conjunto de propriedades que a definam. Eu considero as seguintes proprie-
dades como centrais à orientação a objeto: encapsulamento, ocultação de in-
formações e implementações, retenção de estado, identidade de objeto,
mensagens, classes, herança, polimorfismo e generalização.
O encapsulamento orientado a objeto produz uma estrutura de software
(“objeto”) que compreende um anel de operações protetoras em torno de atri-
butos que representam o estado do objeto. (Em termos de implementação, os
métodos das operações manipulam variáveis que mantêm o estado do objeto.)
Esse encapsulamento assegura que qualquer mudança (ou acesso) a informa-
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 49

ções armazenadas no interior de um objeto se dá por meio das operações do


mesmo.
A ocultação de informações e implementações constitui o desfecho de um
bom encapsulamento. O bom encapsulamento faz com que as informações lo-
cais a um objeto, e as decisões de desenho sobre as implementações locais des-
se objeto, sejam protegidas do olhar atento e intromissão de pessoas estranhas
ao meio.
A retenção de estado é a propriedade pela qual um objeto pode reter in-
formação indefinidamente, inclusive durante os intervalos de ativação de suas
operações.
A identidade de objeto fornece a cada objeto uma identidade permanente
e única, a qual é independente de seu estado atual. O identificador de objeto
é o mecanismo usual para atribuição de uma identidade a determinado objeto.
O identificador de um objeto deve ser conhecido por quaisquer objetos que
desejam enviar mensagens a tal objeto. Uma mensagem consiste do nome de
uma operação definida para o objeto destinatário, juntamente com quaisquer
argumentos passados pela operação ou para ela. Os argumentos podem ser
dados ou indicadores de dados. Todavia, em um meio orientado a objeto puro,
os argumentos somente referem-se a objetos ou indicam objetos.
Os objetos derivados da mesma classe compartilham a mesma estrutura
e comportamento. Uma classe é como um estêncil que é desenhado e progra-
mado para ser a estrutura a partir da qual instâncias da classe — objetos —
são “manufaturados” em run-time. Uma classe pode conter um conjunto de
operações e atributos de classe.
Em princípio, cada objeto tem seu próprio conjunto de métodos para im-
plementar suas operações de instância, e seu próprio conjunto de variáveis
para implementar seus atributos de instância. Na prática, entretanto, objetos
da mesma classe normalmente poupam a utilização de memória pelo compar-
tilhamento da mesma cópia de cada um de seus métodos de instância.
As classes podem formar hierarquias de herança (ou, mais propriamente,
ordens de distribuição) de superclasses e subclasses. A herança permite que
objetos de uma classe se utilizem dos recursos de quaisquer das superclasses
de tal classe. Seletivamente, as operações de uma classe podem ser redefini-
das (“overridden”) em uma subclasse.
O polimorfismo é um recurso pelo qual um único nome de operação pode
ser definido sobre muitas classes diferentes, e pode encarregar-se de diferen-
tes implementações em cada uma dessas classes. Alternativamente, o polimor-
fismo é a propriedade em que se permite que um atributo se refira a (detenha
o identificador de) objetos de classes diferentes em horas diferentes.
50 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O polimorfismo acrescenta uma nova tendência no tocante à ocultação de


implementações e propicia ao método de orientação ao objeto muito de seu po-
der. Por exemplo, um objeto remetente pode enviar uma mensagem sem co-
nhecer a classe exata do objeto destinatário. Contanto que o projetista tenha
conhecimento de que todas as possíveis classes, às quais o objeto destinatário
possa pertencer, têm acesso a determinada operação com o nome e a assina-
tura corretos, a seleção dessa particular operação pode ser deixada para o am-
biente existente durante o run-time.
A sobreposição é um conceito similar ao de polimorfismo: de uma série de
diferentes implementações de determinada operação, é escolhida uma imple-
mentação em run-time para verificar o número e/ou as classes dos argumentos
de uma mensagem. Tanto o polimorfismo como a sobreposição tipicamente pe-
dem por uma ligação (binding) dinâmica, ou em run-time.
A generalização permite que uma classe parametrizada tome uma classe
como um argumento sempre que um objeto for gerado. Isso confere fácil cria-
ção de classes contêiner “genéricas”, que servem como classes “esqueléticas”
em que a específica “carne” pode ser acrescentada durante o run-time. As clas-
ses parametrizadas propiciam as vantagens de um código clonado sem as des-
pesas gerais resultantes da manutenção de réplicas.

1.11 Exercícios
1. (a) Reescreva o algoritmo do problema de navegação do hominóide para
torná-lo mais robusto.
(b) Você consegue verificar algum problema na operação inserirHominóide
(hom: Hominóide, posição: Quadrado, out inserirOK: Booleano), a qual está
definida em Grade?
2. Um objeto conhece o seu próprio identificador? Se a resposta for afirma-
tiva, como um objeto se refere a seu próprio identificador?
3. Por qual motivo seria raro para o mesmo nome de argumento aparecer
tanto como um argumento de entrada de dados como um argumento de
saída de dados na assinatura de uma mensagem? (Suponha que o argu-
mento refira-se a — isto é, detenha o identificador de — um objeto.)
4. Na seção 1.5.3, eu disse que “Na orientação a objeto pura, não há neces-
sidade de dados”. Em outras palavras, tudo é um objeto — um encapsu-
lamento de operações em torno de variáveis, que, em seus próprios
termos, se referem a objetos (por meio das variáveis que os implemen-
tam.) Porém, certamente, deverá haver dados “no fundo disso tudo” ou
então não estaríamos descendo em espiral em uma ladeira sucessiva in-
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 51

finita. Assim, será que realmente todas as coisas podem ser consideradas
um objeto? E o que você me diz dos números inteiros e reais, dos quais
existem milhões de instâncias? Como elas foram criadas?
5. Uma operação de instância pode referir-se a uma variável de classe. En-
tretanto, em um ambiente verdadeiramente orientado a objeto, uma ope-
ração de classe não pode, diretamente, referir-se a uma variável de
instância no interior de um objeto. Por que não?
6. Quando executamos a operação Planador.Novo na seção 1.7, quantos ob-
jetos criamos?
7. Como um programa orientado a objeto é iniciado?
8. O que acontece a todos os seus objetos quando você desliga o computador?
9. O que acontece a todas as suas classes quando você desliga o computador?
10. Você consegue pensar em um único meio de envolver mecanismos robus-
tos de encapsulamento com orientação a objeto em uma linguagem como
a C++?
11. Peter Wegner, em um trabalho de enorme expressão, categorizou os am-
bientes como estruturados em objeto, baseados em objeto, baseados em
classe ou orientados a objeto. A primeira categoria tinha somente encap-
sulamento e retenção de estado; à segunda ele acrescentou a identidade
de objeto; a terceira, acrescentou o conceito da classe; e à última acres-
centou herança e as outras propriedades presentes neste capítulo28. De-
cida qual desses quatro termos é o mais apropriado para a linguagem que
você está utilizando no momento.
12. Eu mencionei neste capítulo que a linguagem Java apóia a herança sim-
ples, mas não a herança múltipla. Isso porque uma classe estende, no má-
ximo, para outra classe. Todavia, uma classe potencialmente implementa
muitas interfaces. Assim, a minha afirmação estava correta? Se você es-
tiver familiarizado com essa linguagem, comente sobre a distinção que
existe em Java entre extensões e implementos sob a forma de mecanismos
de herança.
13. Reescreva o pseudocódigo do hominóide da página 8 na linguagem de pro-
gramação orientada a objeto de sua escolha.
14. Considere uma versão de software comprada por você (ou sua compa-
nhia), que o vendedor alega ser “orientada a objeto”. Que características
do software o vendedor identificou como sendo “orientadas a objeto”?

28 Modifiquei um pouco as definições de Wegner. As definições originais estão em Wegner, 1990.


52 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Você acredita que as justificativas do vendedor são razoáveis? Que bene-


fícios, caso existirem, você notou das características orientadas a objeto
observadas no produto?

1.12 Respostas
1. (a) Duas sugestões: suponha que o quadrado INÍCIO esteja completamen-
te cercado por paredes e suponha que alguém esqueceu de marcar o qua-
drado FINAL na grade. Modifique o algoritmo para enfrentar com
sucesso essas duas situações e quaisquer outras anormalidades que você
tenha considerado, tal como o fato de que não há verificação de segurança
para saber se avançarOK é, de fato, correta.
(b) o problema é este: o que a operação inserirHominóide do objeto Grade
faz com a informação posição: Quadrado (que é a posição de início do ob-
jeto Hominóide)? Ela deve usar essa informação para dizer ao objeto Ho-
minóide qual é sua posição inicial, mas Hominóide não tem uma operação
fixarPosição nele definida! Por causa disso, em vez de se ter inserirHomi-
nóide definida em Grade), deveríamos ter uma operação inserirNaGrade
(grade: Grade, posição: Quadrado, out inserirOK: Booleano) definida em Ho-
minóide. Incidentalmente, também precisamos de uma operação éUmaPo-
siçãoDeParede: Booleano definida em Grade. (Crédito extra: Por quê?)
2. Sim. Um objeto tem uma variável (na realidade, uma constante) — que
você não precisa declarar — que retém o seu próprio identificador. A va-
riável é chamada pela palavra-chave auto, este, este ou Atual (em, respec-
tivamente, Smalltalk, C++, Java ou Eiffel).
3. Isso implica que o destinatário da mensagem tenha alterado o identifica-
dor em um dos argumentos, o que representa uma má prática de dese-
nho. O remetente de uma mensagem deve ter o direito de supor que suas
variáveis detenham os mesmos identificadores, depois de ele ter enviado
suas mensagens, como anteriormente. Poucas linguagens especificamen-
te proíbem esse tipo de prática; os manuais delas contêm afirmativas tais
como “Argumentos são referências de objetos que são passadas pelo valor
e que não podem ser mudadas por código na operação-alvo”.
4. Em uma linguagem orientada a objeto resoluta, tal como Smalltalk, todas
as coisas são um objeto; de fato, a linguagem Smalltalk decididamente
adotou a posição de que “não existem dados”. Por exemplo, em Smalltalk,
a seguinte operação de soma
x<-5+7
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 53

é interpretada como “envie a mensagem mais para o objeto 5, com o objeto


7 servindo de argumento”. A atribuição (<-) é interpretada como “posicio-
nar o identificador do objeto 12 na variável x”.
Todavia, nem todas as linguagens orientadas a objeto são tão austeras
como o Smalltalk. Em uma linguagem como Eiffel, ainda existem data-types
(tal como números inteiros, números reais, chars, Booleanos, e assim por diante).
Entretanto, quaisquer estruturas mais gerais são classes que criam instâncias
de objetos — e não instância de dados — em run-time. Em Eiffel, chega-se a
um acordo sobre o princípio de que “todas as coisas são um objeto” por uma
razão pragmática: tratar números inteiros, caracteres e assim por diante como
data-types permite a compatibilidade de interfaces com o código C.
Em C++, o código C padrão, com data-types padrão, pode ser arbitraria-
mente misturado com o código do C++. Portanto, em C++, “all bets are off!”
(não há qualquer probabilidade!).
Mas, quando digo para as pessoas que a orientação a objeto pura não con-
tém quaisquer dados, elas geralmente “arremessam legumes sortidos” em
mim. Os exemplos mais comuns que as pessoas me contrapõem são as classes
NúmeroInteiro e Datas. Como elas podem ser classes, em vez dos antigos data-
types? Nós temos de dizer NúmeroInteiro.Novo antes que possamos utilizar o
número 5, e Data.Nova antes que possamos utilizar a data 25 de setembro de
1066?
Não. Classes tais como NúmeroInteiro e Datas são conhecidas como classes
literais. Os objetos que pertencem às mesmas são conhecidos como objetos li-
terais. Um objeto literal é simplesmente o que seu valor denota. A maioria dos
objetos literais são também imutáveis: eles nunca mudam de valor.29.
Muito embora cada linguagem orientada a objeto tenha o seu próprio tra-
tamento de classes literais, a maioria das linguagens parte da suposição de
que todas as instâncias de classes literais são predefinidas (ou são criadas in
situ pela conversão de tiras de texto como “15 de março”). Outras linguagens
tratam tais “classes” como data-types padrão, em que as instâncias de fato
constituem muito mais os antigos dados do que objetos.
Em qualquer um dos casos, o processo de gerações a partir de classes li-
terais é desnecessário e ilegal:

29 Como seria a vida sem exceções? Em Smalltalk, por exemplo, uma string é um objeto literal
que pode mudar seu valor. Em Java, Date (Data) é uma classe comum, à qual new é aplicado.
54 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

NúmeroInteiro.Novo; // código ilegal!


Datas.Nova. // código ilegal na maioria das linguagens!

5. A dificuldade reside na seguinte pergunta: “atributo de qual objeto?”


Lembre-se de que, para uma dada classe, podem existir milhares de ob-
jetos circundando-a em run-time. O único meio para a classe “chegar ao”
objeto pretendido é ela ter o identificador desse objeto e enviar uma men-
sagem para ele. O objeto pode então simpaticamente devolver as informa-
ções do atributo para a classe remetente da mensagem.
6. Um. (Não importa quando executamos a operação de classe Novo; nós
sempre criamos exatamente um objeto com, certamente, um identifica-
dor.) Entretanto, esse objeto, que denominamos pl, é uma instância da
classe Planador e, via herança, também uma instância da classe Aeronave.
7. A inicialização de um programa orientado a objeto depende tanto da lin-
guagem como do sistema operacional que você utiliza. Entretanto, exis-
tem três modos comuns pelos quais um sistema operacional pode
transferir controle para o seu programa de aplicação:
• Inicie a execução em uma função principal, que, em uma linguagem
normal de procedimento, ganha controle no start-up do programa.
• Automaticamente, gere um objeto de uma classe Raiz definida pelo
programador, que, na sua geração, inicia a execução de todo o programa.
• Crie um ambiente para navegação orientado a objeto, geralmente com
uma interface gráfica. O usuário/programador pode daí, interativa-
mente, enviar uma mensagem para classe (ou objeto) apropriada(o)
para fazer com que as coisas tenham andamento.
8. Quando você desliga o computador, todos os seus objetos são perdidos na
memória volátil, juntamente com as informações que eles contêm. Se isso
for um problema, então você deve salvar essas informações em disco an-
tes de finalizar seu programa orientado a objeto. Com um object-oriented
database-management system — ODBMS (sistema de gerenciamento de
banco de dados orientado a objeto), você pode salvar seus objetos mais ou
menos diretamente. Mas, se você estiver usando, digamos, um banco de
dados relacional, terá de desempacotar (unpack) as informações dos obje-
tos sob uma forma tabular e normalizada antes de salvá-los.
9. Quando você desliga o computador, nada acontece com suas classes (na
maioria dos ambientes, seja como for). Suas classes são códigos que você
compilou e salvou em seu disco permanente. Esse fato ilustra novamente
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 55

a diferença entre classes, que são formas permanentes, e objetos, que são
unidades mercuriais sempre sofrendo alterações em run-time.
10. Em algumas linguagens, um projetista indescritivelmente maldoso pode
criar certos meios para que pessoas estranhas (outsiders) “pulem sobre os
muros de um objeto” e misturem suas variáveis de forma direta. Uma
possibilidade se dá por meio do mecanismo amigo (friend) da linguagem
C++. Semelhantemente à maioria de outros pecados cometidos por proje-
tistas, este, em particular, é perpetrado em nome da grande entidade de-
nominada eficiência.
11. Agora que você escolheu um termo para sua linguagem em curso, consi-
dere quais das propriedades orientadas a objeto dela (se houver) você con-
sidera a mais valiosa. Se sua linguagem não for plenamente orientada a
objeto, quais das propriedades por mim relacionadas neste capítulo você
mais gostaria de ter presente em sua linguagem? Por quê?
12. A linguagem Java de fato sustenta unicamente a herança simples no sen-
tido da “habilidade em herdar”, que é a forma como descrevo herança nes-
te capítulo. Vamos dizer que uma classe S tenha descrita nela a seguinte
assertiva:
estende C implementa I1, I2
Isso significa que S tem acesso a todas operações de C (métodos de Java).
Em outras palavras, por meio da construção estende, S não só herda a in-
terface de C como também a sua habilidade — o código que faz com que
sua interface trabalhe. Entretanto, S herda responsabilidade, e não habi-
lidade, por meio da construção implementa. Neste exemplo, o desenhis-
ta/programador de S deverá proporcionar métodos de Java operantes
para todas as operações definidas nas interfaces I1 e I2.
13. A seguir é o que você poderia trazer à baila para a reescrita do hominóide
em Java. Eu assumo que o algoritmo está contido na operação navegar,
que será uma operação da classe Grade. (Dessa maneira, qualquer refe-
rência ao objeto grade em si será feita por meio da notação this.) Eu tam-
bém dou como certo que o objeto grade é propriamente inicializado (por
exemplo, que quaisquer hominóides previamente inseridos dentro dele te-
nham sido removidos).
56 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

public booleano navegar ( )


{ // Posiciona um novo hominóide no quadrado início da grade e navega
// o hominóide para o quadrado final da grade, exibindo o hominóide em cada
// etapa. Retorna false se ocorrerem problemas no algoritmo; true de outra forma.

Hominóide hom = novo Hominóide ( ); // cria nova instância de Hominóide,


// que será referida como hom

int umQuadrado = 1; // constante


int inicialContagemDeViradas;

Quadrado iniciarQuadrado = this.iniciar;


booleano inserirOK = this.inserirHominóide (hom, iniciarQuadrado);
// inserirHominóide retorna true se bem sucedido

if (! inserirOK) return false;


// aborta se o hominóide não colocado OK na grade
// posiciona o hominóide no sentido correto:
// vira o hominóide 4 vezes no máximo ou até o hominóide ter uma
// trajetória livre a sua frente

inicialContagemDeViradas = 1;
while (inicialContagemDeViradas <= 4 && hom.apontandoParaParede)
{hom.virarÀEsquerda( ); inicialContagemDeViradas ++;}
//endwhile

this.exibir( ); //exibe a grade................


hom.exibir( ); //... e o hominóide

while (hom.posição != this.finalizar)


{if (hom.apontandoParaParede)
{hom.virarÀEsquerda( );
if (hom.apontandoParaParede) {hom.virarÀDireita( ); hom.virarÀDireita( );}
//endif
}//endif
CAP. 1 AFINAL DE CONTAS, O QUE SIGNIFICA SER ORIENTADO A OBJETO? 57

hom.avançar (umQuadrado);
hom.exibir;

}//endwhile
//o hominóide está no final — sucesso!

return true;

}//end navegar
B reve história da orientação
a objeto
2.Breve História da Orientação a Objeto
BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A gora que examinamos as propriedades intrínsecas da orientação a objeto,


vamos examinar como ela se enquadra em um panorama mais amplo do
desenvolvimento de software.
Ao ouvir que o professor Wolfgang Pauli tinha proposto uma nova e fun-
damental partícula (a µ-méson ou muon), o professor Isidor I. Rabi imediata-
mente retrucou: “Quem mandou fazer isso?” Eu começo este capítulo listando
algumas das pessoas que “mandaram fazer” a orientação a objeto. Em segui-
da, posiciono a orientação a objeto em um contexto social, ao discutir as ati-
tudes tomadas em relação a essa abordagem de software. E, mais adiante,
situo a orientação a objeto em um contexto de engenharia fazendo um paralelo
entre a orientação a objeto e a eletrônica. Finalmente, indico que tipo de orien-
tação a objeto poderá realmente ser boa, tendo em vista os programadores,
analistas e gerentes que constituam o seu local de trabalho.

2.1 De Onde Surgiu a Orientação a Objeto?


Ao contrário de muitos avanços da descoberta humana, a orientação a objeto
não foi concebida em um único momento. Em vez de constituir a inspiração
do tipo “Eureka!”, de uma única pessoa em uma banheira, ela foi resultado do
“transbordamento” do trabalho de muitas pessoas em banheiras durante vá-
rios anos. Os conceitos de orientação a objeto abordados no Capítulo 1, são
como diversos afluentes que escoaram juntos, quase por acidente histórico,
para formar o “rio” da orientação a objeto.
Assim — com minhas desculpas antecipadas se eu deixar de lado o seu
predileto pesquisador de software — aqui (aproximadamente em ordem cro-

58
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 59

nológica) estão algumas das pessoas que, suponho, fizeram significativas con-
tribuições, teóricas e práticas, para o “dilúvio” da orientação a objeto.

2.1.1 Larry Constantine


Uma vez que nenhuma relação de contribuintes de software seria completa
sem ele, vamos iniciar com o pré-diluviano Larry Constantine. Embora nos
anos 60 Constantine não tenha feito coisa alguma dirigida ao tópico da “orien-
tação a objeto”, ele realmente pesquisou de forma intensiva os critérios fun-
damentais para um bom desenho de software (Constantine, 1968). Na
realidade, ele foi uma das primeiras pessoas até mesmo a sugerir que o soft-
ware poderia ser desenhado antes de programado. Muitas das noções de Cons-
tantine (tais como acoplamento [coupling] e coesão [cohesion]) provaram sua
aplicabilidade no mundo atual da orientação a objeto.

2.1.2 O.-J. Dahl e K. Nygaard


Dahl e Nygaard introduziram diversas idéias que agora são parte da orienta-
ção a objeto. O melhor exemplo é a idéia da classe, que primeiro apareceu na
linguagem Simula (Dahl e Nygaard, 1966).

2.1.3 Alan Kay, Adele Goldberg e outros


Kay, Goldberg e colegas introduziram as primeiras corporificações da lingua-
gem Smalltalk no Centro de Pesquisas de Palo Alto da Xerox (Xerox Palo Alto
Research Center) por volta de 1970 (Kay, 1969). Essa pesquisa forneceu-nos
muitos dos conceitos que agora consideramos centrais à orientação a objeto
(tais como mensagens e herança). Muitas pessoas ainda consideram a lingua-
gem e o ambiente Smalltalk (veja, por exemplo, (Goldberg e Robson, 1989)
como a implementação mais pura da orientação a objeto existente nos dias de
hoje.

2.1.4 Edsger Dijkstra


Dijkstra, em The Conscience of Software Correctness, tem nos causado uma
culpa constante. Em seu primeiro trabalho, ele propôs algumas idéias sobre
como construir software em camadas de abstração com uma estrita separação
semântica entre camadas sucessivas. Isso representa uma forma de encapsu-
lamento muito forte, fato esse central à orientação a objeto.
60 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2.1.5 Barbara Liskov


Liskov realizou progressos significativos nos anos 70 sobre a teoria e imple-
mentação do abstract data-type — ADT (tipo de dado abstrato), formador de
uma base para a orientação a objeto. Um dos resultados mais expressivos do
trabalho de Liskov foi a linguagem CLU, na qual a noção de representações
de dados internos ocultos é solidamente sustentada. Veja, por exemplo, Liskov
e outros, 1981.

2.1.6 David Parnas


Em um artigo “divisor de águas”, Parnas escreveu sobre os princípios de uma
boa construção modular de software (Parnas, 1972). Embora as construções da
orientação a objeto transcendam os módulos de procedimento convencionais,
muitos dos princípios básicos da ocultação de informações de Parnas ainda se
aplicam a sistemas orientados a objeto.

2.1.7 Jean Ichbiah e outros


Ichbiah e seu grupo desenvolveram a Linguagem de Programação “Green”,
que o Departamento de Defesa dos Estados Unidos adotou como a linguagem
Ada (atualmente conhecida como Ada-83).1 Duas das construções presentes
na Ada-83 (generalização e pacote) estão bem no âmago da orientação a objeto.
Uma versão posterior dessa linguagem, Ada-95, suporta mais plenamente a
orientação a objeto.

2.1.8 Bjarne Stroustrup


A linguagem C++ tem uma interessante genealogia. Existia no passado uma
linguagem denominada BCPL, devida a Martin Richards (Richards e Whitby-
Strevens, 1980). Essa linguagem criou uma linguagem B, que era uma abre-
viação (!) para BCPL. A linguagem B criou a C, e a C (por meio do trabalho
de Stroustrup) originou a linguagem orientada a objeto C++.
Como Stroustrup a descreve (Stroustrup, 1991, p. 4), a “criação C++” se
parece com uma criança incerta:
C++ foi essencialmente projetada para que o autor e seus ami-
gos não tivessem de programar em Assembler, C ou várias ou-
tras linguagens modernas de alto nível. Sua finalidade principal

1. Ada® é uma marca registrada pertencente ao governo dos Estados Unidos.


CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 61

era escrever bons programas com maior facilidade, e que fossem


mais agradáveis para o programador individual.
Nunca houve um projeto, feito no papel, de C++; o projeto, a documenta-
ção e a implementação aconteceram e prosseguiram simultaneamente.
Uma vez que a orientação em objeto de C++ foi enxertada sobre uma lin-
guagem anterior, não orientada a objeto e um tanto de baixo nível, sua sintaxe
nem sempre é clara. Todavia, apesar da ascensão da linguagem Java, C++
ainda é a linguagem orientada a objeto mais amplamente utilizada. Sua li-
nhagem em C capacitou-a de portabilidade através de muitas máquinas e sis-
temas operacionais, e, em decorrência, fez com que aumentasse imensamente
a popularidade da programação orientada a objeto. A esse respeito, a contri-
buição de Stroustrup para o campo foi imensa.

2.1.9 Bertrand Meyer


A força de Meyer é a mistura das melhores idéias da informática com as me-
lhores idéias da orientação a objeto. O resultado: uma linguagem e um am-
biente chamados de Eiffel. Eiffel é uma raridade no mundo do software, pois
atrai simultaneamente acadêmicos, engenheiros de software e aquele pessoal
nas trincheiras que precisa de um código robusto. Não importa linguagem
orientada a objeto de sua preferência, definitivamente você deve aprender os
conceitos que existem por detrás da linguagem Eiffel se quiser tornar-se um
verdadeiro profissional orientado a objeto.2

2.1.10 Grady Booch, Ivar Jacobson e Jim Rumbaugh


Esses três personagens são conhecidos coletivamente pelo apelido de Os Três
Amigos.3 Muito embora cada um deles tenha seu próprio direito à fama devido
a seus trabalhos individuais orientados a objeto, como um grupo esse direito
surge de suas colaborações no final dos anos 90 no tocante à racionalização da
notação orientada a objeto (que em 1990 correspondia a uma verdadeira Torre
de Babel). O resultado do trabalho deles foi a Unified Modeling Language
(UML), uma linguagem de modelagem unificada gráfica contendo uma forma
de expressão visual e uma escora semântica perfeita. Eu trato da UML nos
Capítulos 3 a 7 deste livro.

2. Um excelente livro para o aprendizado de Eiffel é a obra de Wiener, 1995.


3. O apelido deles refere-se ao nome de um filme de John Landis — mas até o momento não vi
muita semelhança entre Ivar Jacobson e Steve Martin. Incidentalmente, na América do Sul,
esse grupo de metodologistas é conhecido como Os Três Camaradas.
62 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2.2 A Orientação a Objeto Atinge a Maioridade


Nesta seção, descrevo como a indústria de software reagiu com o glorioso ad-
vento da orientação a objeto.
Os biólogos mais antigos gostam de proferir este velho ditado: “A ontoge-
nia recapitula a filogenia.” Esse provérbio, difícil de ser falado, significa que
o desenvolvimento de um embrião individual imita freqüentemente o desen-
volvimento evolucionário de suas espécies como um todo. Um exemplo seria o
desenvolvimento transiente de guelras no embrião humano. Naturalmente,
existe uma enorme diferença na escala do tempo. A ontogenia leva meses, en-
quanto a filogenia leva eras para acontecer.
Embora esse antigo ditado da biologia esteja muito desacreditado, um
novo provérbio ligado à engenharia de software afirma: “A história da enge-
nharia de software orientada a objeto recapitula a história da engenharia de
software convencional”. Na verdade, há uma enorme diferença na escala do
tempo. Convencionalmente demoraríamos décadas (ou mais ou menos isso!)
até participarmos ativamente em construções de bancos de dados e processos;
com o software orientado a objeto estaremos lutando corpo a corpo em questão
de anos.
O desenvolvimento de software começou simplesmente com a programa-
ção. À medida que o tamanho dos sistemas e a experiência humana aumenta-
ram, as pessoas compreenderam que escrever meramente códigos para uma
aplicação em uma torrente de consciência já não era o bastante. Mesmo se
uma aplicação desse tipo miraculosamente funcionasse, seu código seria tão
desorganizado que qualquer mudança nele se tornaria praticamente impossí-
vel.
Entrou em cena então o desenho. O desenho de software possibilitou a
disposição de um plano para uma organização racional do código antes que
uma única linha fosse realmente “talhada” no bloco de codificação. Essa idéia
radical até mesmo permitiu que as pessoas tivessem acesso a problemas po-
tenciais de manutenção na fase do “tigre-de-papel”.
Até esse momento, tudo bem! Agora, nós éramos capazes de produzir soft-
ware refinadamente arquitetado. Entretanto, alguns indivíduos mais inteli-
gentes notaram que uma grande parcela desse requintado software não
atendia às necessidades dos clientes. Para satisfazer à paixão singular do
usuário por um software de utilidade, foi introduzida uma análise sistemática
mais rigorosa.
Finalmente, fomos agraciados com as ferramentas da Engenharia de
Software Assistida por Computador (Computer-Aided Software-Engineering
— CASE). No princípio, essas ferramentas eram extremamente fracas. Mas,
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 63

depois, quando elas passaram por correções e tornaram-se um pouco mais for-
talecidas, foi concedido a elas o direito de mudarem seus nomes sob a tutela
do Programa de Proteção Federal (Federal Protection Program). Hoje, as pri-
mitivas ferramentas CASE são conhecidas como ferramentas de modelagem
automatizadas. A modelagem de ferramentas ajuda-nos no caso das discipli-
nas ligadas à análise de requisitos e ao desenho e construção de software. Elas
também fazem com que o desenvolvimento e a manutenção do software se tor-
nem mais gerenciáveis.
Durante toda a história do software, as pessoas tentaram atingir a reu-
tilização. Infelizmente, a maioria das unidades procedimentais de código não
são suficientemente independentes para serem reutilizadas independente-
mente. Mas, agora, com a orientação a objeto, temos outra chance de “ganhar
o paraíso” da reutilização.
Entretanto, a orientação a objeto não garante milagres. Se as classes de
objeto não forem desenhadas cuidadosamente, por meio de diretrizes iguais às
que exponho no decorrer deste livro, a orientação a objeto será incapaz de pro-
porcionar um software confiável e reutilizável.
Caso essa triste situação não seja superada, tudo o que obteremos para
reutilizar serão as histórias relatando o infortúnio de gerentes que fracassa-
ram ao tentarem se valer dos benefícios da orientação a objeto.
Como mencionei anteriormente, a história da orientação a objeto corre
paralela à história corrente predominante do software. Entretanto, com a
orientação a objeto, a progressão da implementação para a abstração ocorreu
em um ritmo extraordinário. A programação orientada a objeto primeiramen-
te atingiu a popularidade nos anos 80. Essa mesma década presenciou a in-
trodução tanto do desenho orientado a objeto como da análise orientada a
objeto. Os sistemas de gerenciamento de bancos de dados orientados a objeto
(ODBMS) e as ferramentas de modelagem orientadas a objeto começaram a
“engatinhar” por volta de 1990.
A rápida chegada desses campos orientados a objeto levou a uma estra-
nha amnésia. Algumas das pessoas que passaram pela ontogênese orientada
a objeto repentinamente se esqueceram de sua filogênese predominante. O
slogan delas tornou-se: “Tudo o que conhecíamos antes de 1990 não tem qual-
quer valor!” Esses indivíduos eram os revolucionários orientados a objeto im-
petuosos, intocáveis e reacionários. Eles denunciaram o então corrente
“sistema (establishment) de software” como os “tigres de papel” burgueses e
imperialistas de uma dinastia COBOL retrógrada, os quais mereciam nada
mais do que uma passagem de ida a Ekaterinburg.
64 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Agora, o fervor revolucionário deles amainou. A revolução voltada a obje-


tos foi ganha, e seus instigadores e discípulos tornaram-se, eles próprios, par-
te do atual sistema (establishment). Muitas das ferramentas e técnicas que
hoje encontramos no mercado de software, em uma forma rotineira e casual,
estão baseadas na orientação a objeto. Isso é especialmente verdadeiro no
mundo do cliente-servidor e de outros sistemas distribuídos.
Mas o reino do software está em constante revolução. O próximo avanço
na direção para o paraíso dos programadores, o software de componentes dis-
tribuídos, já se encontra entre nós. Com componentes distribuídos, o software
surge com mais benefícios do que os antigos revolucionários de software pro-
meteram em seus primeiros e acalorados discursos.
Eu me dedico à aplicação da orientação a objeto a componentes de soft-
ware no Capítulo 15.

2.3 Orientação a Objeto Como Disciplina de Engenharia


Na década de 80, Brad Cox observou que, sob muitos aspectos, um objeto de
software parecia-se com um circuito integrado de hardware (IC) sobre o qual
está baseada uma grande parcela da vida de nosso dia-a-dia (Cox, 1986). Eu
me lembrei dessa agradável analogia recentemente, quando os arqueólogos da
University of Washington estavam procurando por papéis em meu escritório.
Eles desenterraram um livro denominado Introduction to Radar Systems, de-
vido a Merrill Skolnik, no qual o autor faz as seguintes observações:
A engenharia eletrônica pode ser classificada de acordo com: (1)
componentes, (2) técnicas e (3) sistemas. Componentes consti-
tuem os blocos de construção básicos, que são combinados utili-
zando técnicas para gerar um sistema.
Se fizermos uma pequena modificação na prosa de Skolnik, substituindo
“eletrônica” por “de software”, e “componentes” por “classes”, teremos o se-
guinte:
A engenharia de software pode ser classificada de acordo com:
(1) classes, (2) técnicas e (3) sistemas. Classes constituem os
blocos de construção básicos, que são combinados utilizando téc-
nicas para gerar um sistema.4

4. No Capítulo 15, exploro uma construção de software que é análoga à placa de circuito impres-
so eletrônica. Essa construção é na verdade chamada de componente.
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 65

Embora essa imagem seja sedutora, não podemos esquecer que a escolha
de circuitos de valor a serem encapsulados em chips depende da habilidade do
engenheiro em identificar abstrações proveitosas. As pessoas correriam apres-
sadamente para comprar ICs com utilização em amplificadores operacionais,
amplificadores de áudio, contadores de tempo, line-drivers e assim por diante.
Porém, ninguém nem mesmo chegaria a dar um passo para comprar um IC
de ultra-grande-escala, que tivesse utilização em transistores, indutores e re-
sistores randômicos. Antes de o primeiro IC útil ser construído, os engenheiros
levaram décadas para descobrir os modelos mais proveitosos, que foram emer-
gindo em sistemas eletrônicos, uns após os outros.
Em software, por analogia, devemos nos certificar de que as classes que
desenvolvemos são baseadas em abstrações fortes, sadias e de fácil manejo.
Classes como Cliente, e a adorável, antiga e felpuda, Pilha, muito provavelmen-
te receberão uma ovação em pé; classes tais como Egabragrettu são muito mais
passíveis de serem jogadas em depósitos de lixo nos arredores da cidade.
O segundo ponto de Skolnik é sobre técnicas. Uma vez que os ICs que não
pudessem ser combinados praticamente seriam inúteis, é auspicioso saber que
os engenheiros eletrônicos têm à disposição deles placas de circuito impresso
(PCBs), que retém os ICs em conjunto.
De maneira similar, no desenvolvimento de software orientado a objeto,
nós também devemos nos encarregar do nível “macro” do projeto. Esse nível
lida com os meios pelos quais as classes (e os objetos gerados pelas classes em
run-time) estão interligados. Claramente, haverá uma forte correlação entre o
que desenhamos em nível de intraclasse e o que desenhamos neste nível mais
alto, de interclasse. Isso é esperado, é certo, uma vez que o layout de uma pla-
ca de circuito impresso depende, até certo ponto, do projeto dos ICs que ficam
nela posicionados5.
Poderão existir bons e ruins desenhos orientados a objeto, tanto no nível
de intraclasse como no nível de interclasse. Assim, bons sistemas orientados
a objeto, similarmente a bons sistemas eletrônicos, dependem não somente
das abstrações de alta qualidade como também das técnicas de alta qualidade
destinadas à construção com essas abstrações. As Partes II e III deste livro
tratarão dessas questões. Mas, primeiro, devemos responder a uma pergunta
básica: Para que serve a orientação a objeto?

5. Se você estudou desenho estruturado padrão (veja Page-Jones, 1988, por exemplo), recordará
um princípio similar: o acoplamento entre um conjunto de módulos depende grandemente da
coesão de cada módulo no conjunto.
66 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2.4 Para Que Serve a Orientação a Objeto?


O título desta seção é um convite para os cínicos e fanáticos.
Alguns reacionários diriam que a orientação a objeto não serve para coisa
alguma; trata-se meramente de um culto religioso, ou de uma conspiração glo-
bal baseada em algum lugar na Costa Oeste. Alguns revolucionários iriam di-
zer que a orientação a objeto é a primeira e única solução miraculosa para
todos os nossos problemas de software. Não só ela limparia as janelas, como
também fatiaria e cortaria legumes em pequenos cubos, enceraria os assoa-
lhos e daria os últimos retoques em sobremesas — tudo sob a mais recente
arquitetura distribuída, de multicamada (multitiered), habilitada pela Web.
Eu não fico em qualquer um desses extremos. Acredito que a orientação
a objeto é útil, que não é um milagre, que nem mesmo chega a ser perfeita, e
que sua utilidade específica depende de como você a coloca para trabalhar em
seu processo de desenvolvimento de software.
Nenhuma abordagem respeitável de engenharia de software deveria ser
tratada como a “coqueluche do ano”. A coqueluche do ano trata-se da aborda-
gem de um tema que se transforma na maior “onda” durante alguns meses do
ano6. Seus adeptos a adotam histericamente como a solução para todos os pro-
blemas errantes de software. Os céticos aderem à última hora pelas forças do
fanatismo. Mais tarde, depois que a abordagem foi usada e abusada indiscri-
minadamente para o alcance de efeitos medíocres, seus adeptos a abandonam
em massa e rumam como um enxame de abelhas na direção da coqueluche do
ano vindouro. Se o seu local de trabalho “surfa em tecnologia”, movendo-se ra-
pidamente de coqueluche a coqueluche, você provavelmente se beneficiará
muito pouco da orientação a objeto.
A orientação a objeto também não se parece com uma panacéia, uma so-
lução insensata para o problema que está atacando o seu local de trabalho.
Em lugar disso, como veremos neste livro, a orientação a objeto é uma abor-
dagem poderosa, embora desafiadora, aplicada a desenvolvimento de software.
Em um local de trabalho maduro e profissional, não se brinca com a orienta-
ção a objeto tendo todos os cérebros desligados; em vez disso, trabalha-se ar-
duamente na abordagem da orientação a objeto, integrando nos planos da
empresa a longo prazo para o desenvolvimento de software profissional.
No decorrer desta seção, discuto os efeitos potenciais da orientação a ob-
jeto em seis atividades relacionadas a software em um típico local de trabalho.

6. Veja Page-Jones para mais detalhes sobre a “coqueluche do ano”.


CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 67

2.4.1 Análise dos requisitos de usuários


Os defensores das técnicas estruturadas nunca chegaram a um acordo sobre
onde colocar a fronteira entre a análise de processos e a análise de dados. O
mundo dos processos do diagrama de fluxo de dados sempre coexistiu inquie-
tamente — com o mundo dos dados do diagrama — de relacionamento entre
entidades. A análise de processos e a análise de dados tornaram-se placas tec-
tônicas em vias de colisão, encontrando-se em alguns pontos, afastando-se
umas das outras sismicamente e, em outros pontos mais, perdendo-se total-
mente. Houve um “choque” especialmente notável em modelagens de sistemas
em tempo real, onde (por exemplo) a correspondência entre o processo de con-
trole e o modelo de dados era muitas vezes obscura.
Uma abordagem orientada a objeto destinada à análise combina a inves-
tigação de processos e a investigação de dados bem no começo do ciclo de vida.
Muito embora não possamos dizer “análise de processos e análise de dados”
quando falamos sobre a orientação a objeto (conforme mencionado no Capítulo
1) — “a análise dinâmica e estática” se enquadraria melhor — a utilização em
up-front de conceitos orientados a objeto traz uma harmonia mais suave a es-
ses dois aspectos da análise. Algumas pessoas estavam desejosas de ver a mis-
tura de processos e dados da orientação a objeto, como a fusão de espaço e
tempo desenvolvida por Einstein em suas teorias da relatividade, muito em-
bora eu deva dizer que acho essa comparação um tanto extravagante.

2.4.2 Desenho de software


Em desenho, a orientação a objeto é tanto uma bênção como uma perdição.
A orientação a objeto é uma bênção porque permite que o desenhista es-
conda, por detrás das paredes cênicas o encapsulamento, os objetos de aversão
existentes em software, tais como: estruturas de dados convolutas, lógica com-
binatorial complexa, relacionamentos meticulosos entre procedimentos e da-
dos, algoritmos sofisticados, assim como horríveis drivers de dispositivos.
A orientação a objeto é uma perdição porque as estruturas que ela em-
prega (tal como encapsulamento e herança) podem, em seus próprios termos,
tornar-se complexas. Na orientação a objeto também é muito fácil criar uma
rede górdia de interligações insolúveis, que, ou são impossíveis de se cons-
truir, ou resultam em um sistema que opera praticamente tão rápido quanto
“um cavalo em uma corrida de sacos”. Evitar esses problemas é um dos prin-
cipais desafios para os desenhistas da orientação a objeto.
A finalidade deste livro é apresentar algumas idéias, técnicas e princí-
pios, com os quais se poderiam enfrentar os desafios do desenho em orientação
68 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

a objeto. Na Parte II do livro, introduzo as características mais úteis da UML,


que constitui a maneira mais comum de descrever e explorar decisões sobre
desenho.
Na Parte III, forneço diversos princípios e critérios de desenho por meio
dos quais você pode julgar um desenho. Utilizando esses princípios e critérios,
você pode criar construções orientadas a objeto que lhe ajudarão quando for
construir sistemas a partir delas, e que, assim mesmo, serão independente-
mente sustentáveis. Muito embora o desenho orientado a objeto às vezes de-
mande um trabalho excessivamente árduo, é sabido que, quando bem-feito, ele
o recompensará com o abrandamento de grande parcela de complexidade, o
que talvez não seja possível com outras técnicas de desenho.

2.4.3 Construção de software


As qualidades mais freqüentemente observadas em sistemas construídos no
modo orientado a objeto são: reutilização, confiabilidade, robustez, extensibi-
lidade, distributividade e armazenabilidade.

Reutilização
A orientação a objeto acentua a reutilização porque ela promove um novo uso
do código em nível de classe, em vez de em nível de sub-rotina individual. Ao
desenvolver e cultivar uma biblioteca de classes para suas aplicações em seu
local de trabalho, você está, na prática, criando uma linguagem nova e de mui-
to alto nível feita sob medida especificamente para atender suas necessidades.
Parece, empiricamente, que a classe de objetos é um organismo suficien-
temente sofisticado capaz de migrar de aplicação à aplicação pela sua compa-
nhia, como uma unidade de software independente. Pelo menos, isso é
verdadeiro se a classe carrega consigo o arcabouço de classes subsidiárias ne-
cessário para a realização do trabalho dela. (Exploro esse tópico no decorrer
das seções 9.1 e 9.2.)

Confiabilidade
Códigos confiáveis trabalham repetida e consistentemente. Seu código atingi-
rá essas qualidades somente se você puder de alguma forma verificar a exati-
dão do mesmo. O código orientado a objeto empresta-se a si mesmo para
verificação por meio do uso de certas asserções denominadas de invariantes de
classe. Uma invariante de classe é uma condição que todo objeto de uma dada
classe deve satisfazer. (Por exemplo, uma invariante da classe Pessoas pode
ser dataDeNascimento<= dataDeHoje.)
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 69

As invariantes de classe (e outras asserções que também abordo no Ca-


pítulo 10) possibilitam a verificação de códigos do começo ao fim. Em um en-
saio ou inspeção, você é capaz de verificar que um desenho e seu código
resultante satisfazem as invariantes pretendidas. Se bem que — mesmo uti-
lizando a orientação a objeto — você nunca pode provar que um código está
absolutamente correto, a orientação a objeto facilita a verificação do que o có-
digo fará em comparação com o que se espera que ele possa fazer.7

Robustez
A robustez em software é a habilidade de eles se recuperarem facilmente sem-
pre que ocorrer alguma falha. As falhas típicas são violações na asserção, vio-
lações na memória, erros em dispositivos externos e extravasamentos
aritméticos. O software torna-se robusto quando é capaz de capturar uma fa-
lha inesperada (geralmente chamada de exceção [exception]) e é capaz de exe-
cutar uma rotina (geralmente chamada de manipulador de exceção [exception
handler], ou cláusula de resgate [rescue clause]) para recuperar-se de uma fa-
lha.
Muitas linguagens e ambientes modernos orientados a objeto suportam a
detecção e o manuseio de exceções e, dessa forma, encorajam o desenvolvimen-
to de software robusto. Uma excelente maneira de atingir robustez para o có-
digo orientado a objeto é combinar a idéia de asserções e invariantes com a de
manipuladores de exceção. Em alguns meios orientados a objeto, você conse-
gue monitorar invariantes de classe e outras asserções em run-time, e também
ter o software habilmente recuperado se (Deus me livre!) uma asserção for vio-
lada.
A alternativa para o manuseio desses manipuladores é não termos esse
recurso: nunca detecte exceções e simplesmente deixe que o software pare
quando ocorrer uma exceção. Certamente, isso não é robustez!

Extensibilidade
Uma fácil extensibilidade de software depende tecnicamente do que é chama-
do de “homomorfismo entre o domínio da especificação e o domínio da imple-
mentação”. Ai! Em palavras menos formais, isso significa que você deverá
fazer com que o formato da solução se encaixe no formato do problema. Ao fa-
zer isso, é assegurado que uma pequena mudança de usuário não irá se tornar

7. A razão essencial é que o conceito de correção não é algo absoluto que possa ser determinado
para todos os observadores, mas sim algo relativo ao quadro de referência de um observador
em particular. Em outras palavras, a correção é fundamentalmente subjetiva.
70 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

um grande pesadelo de sistema. E, quando você modificar seu código orienta-


do a objeto, vai descobrir que estará criando menos problemas ocultos em lu-
gares remotos, escuros e úmidos. Devido ao fato de a orientação a objeto criar
unidades de software com abstrações de maior nível, mais próximas da reali-
dade, essa é uma técnica para se aproximar muito mais desse “homomorfis-
mo” do que as técnicas tradicionais.
A extensibilidade e a herança freqüentemente caminham lado a lado. Os
usuários muitas vezes desejam estender um sistema, pela adição de variações
em um tema já estabelecido. Por exemplo, “Em vez de termos simplesmente
clientes, agora queremos ter clientes nacionais e estrangeiros.”8 Utilizando-se
da orientação a objeto, você pode fazer essas extensões incrementalmente,
acrescentando subclasses sucessoras segundo uma superclasse já implementada.

Distributividade
Em 1989, um órgão conhecido como Grupo de Gerenciamento Dirigido a Ob-
jeto (Object Management Group — OMG) empreendeu uma tarefa digna de
Hércules: reunir uma grande quantidade dos principais vendedores de hard-
ware e software para que chegassem a um acordo sobre normas de interope-
rabilidade destinadas à orientação a objeto. Eu fiquei maravilhado por eles
terem se incumbido de um empreendimento desse porte — e fiquei assombra-
do quando obtiveram êxito!
O fruto mais visível desse empreendimento foi a criação da Common Ob-
ject Request Broker Architecture (CORBA), uma arquitetura para software
concordante e padronizada, direcionada a dar suporte a sistemas orientados a
objeto distribuídos por plataformas múltiplas.9 O fato em si é impressionante.
Entretanto, a CORBA também permite que objetos “falem uns com os outros”,
não só por meio de máquinas similares, mas também por meio de máquinas
de diferentes modelos, executando sistemas operacionais diferentes e conecta-
dos por uma variedade de redes distintas.
Sob a tutela da CORBA, os objetos por si próprios podem até mesmo ser
criados a partir de classes escritas em diversas linguagens, e compiladas em
diferentes compiladores. E, acima de tudo, uma implementação de CORBA in-
clui vários serviços padrão (tais como: replicação [replication], representação
[ p roxy], manipulação de relacionamentos [relationship-handling] e mediação
de transações (transaction-mediating), que automatizam as partes enfadonhas

8. Entretanto, esse é um exemplo fácil. Como veremos nos Capítulos 11, 12 e 13, a maioria dos
exemplos na vida real não é tão clara e fácil.
9. Para mais detalhes sobre a CORBA, veja Mowbray e Zahavi, 1995 e Orfali e outros, 1996.
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 71

de códigos que, de outra forma, você teria de realizar manualmente para con-
figurar seu sistema distribuído.
Em outras palavras, com a arquitetura CORBA a natureza heterogênea
e distribuída das plataformas torna-se transparente a você, ao desenhista e ao
programador da aplicação. Você pode escrever mensagens da mesma forma
que no modo habitual do processador comum, e deixar que os serviços CORBA
lidem com muitos dos detalhes “sujos” da infra-estrutura.

Armazenabilidade
Esta seção não estaria completa sem uma menção dos sistemas de gerencia-
mento de bancos de dados orientados a objeto. Um ODBMS é útil se você es-
tiver construindo qualquer aplicação orientada a objeto, e é especialmente
proveitoso se a sua aplicação manipular sons ou gráficos, nenhum dos quais
é facilmente retido em uma forma tabular relacional padrão.
Um ODBMS manterá objetos de classes arbitrárias (não somente classes
tais como String, Números Reais, Números Inteiros e Data, mas também Cliente,
Aeronave, MapaDaCidade, Videoclipe e assim por diante), e proporcionará o en-
capsulamento orientado a objeto, a herança, o polimorfismo e outras caracte-
rísticas importantes orientadas a objeto. A maioria dos ODBMS vem com uma
linguagem de perguntas (tal como a linguagem de perguntas a objeto, OQL)
que substitui o SQL dos DBMSs relacionais.

2.4.4 Manutenção de software


Reutilização, confiabilidade, robustez e extensibilidade são os quatro pilares
da manutenção. A manutenção, é certo, representa o ponto em que os locais
de trabalho consomem a maior parte de seu dinheiro. Pelo fato de ela aperfei-
çoar estas quatro qualidades, a orientação a objeto pode reduzir os custos da
manutenção do sistema nas seguintes formas:

• A reutilização reduz o corpo de código total que o seu local de trabalho


deve manter. Isso reduz a quantidade do novo código que deve ser es-
crita e, mais tarde, mantida para um sistema, especialmente depois
de um ou dois projetos orientados a objeto.
• A confiabilidade no software diminui as queixas de descontentamento
e os pedidos angustiados de ajuste pelos usuários.
• A robustez assegura que o software que passa por manutenção contí-
nua não se desintegre totalmente em pedaços na mesa de operação.
72 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• A extensibilidade tira vantagem da tendência natural dos usuários de


solicitar modificações do tipo “deslocamento de base” para seus siste-
mas, por meio das quais eles continuariam a pedir muitas modifica-
ções, comparativamente de menor importância, no software existente.

2.4.5 Utilização de software


As aplicações gráficas sempre foram uma opção popular para a orientação a
objeto. Em particular, as pessoas muitas vezes implementam interfaces gráfi-
cas do usuário (Graphical User Interfaces — GUIs) por meio da orientação a
objeto. Há duas razões para isso: a primeira é conceitual; a segunda, imple-
mentacional.
Conceitualmente, a metáfora da orientação a objeto se adapta bem com
a interface janela/mouse/ícone típica. Digamos que você tenha um ícone em
uma tela. Esse ícone pode ser uma representação visível de um objeto, diga-
mos um cliente. Dê um clique no ícone com um mouse para selecionar esse
cliente. A seguir, faça aparecer um menu. As opções no menu podem corres-
ponder aproximadamente aos métodos aplicáveis a um cliente. Por exemplo,
poderia existir uma opção de alterarEndereço, uma outra de reavaliarLimiteDe-
Crédito e assim por diante. Igualmente, um menu de cliente doméstico poderia
ser diferente de um menu de cliente estrangeiro. Cada menu relacionaria so-
mente as atividades comerciais aplicáveis ao tipo particular de cliente.
Mesmo o polimorfismo — a habilidade de um método assumir diferentes
significados ou implementações com respeito a diferentes classes — talvez
aparecesse na interface do usuário. Digamos que você tenha um ícone na tela
representando um objeto planilha eletrônica e um outro representando um ob-
jeto documento. Quando você dá um clique duplo no item de menu Abrir, você
pode anexar tanto o programa de planilhas eletrônicas quanto o programa de
processamento de textos no objeto, dependendo de qual dos dois itens foi real-
çado (selecionado). Em outras palavras, a versão particular do método abrir
que está sendo executada depende de a classe de objeto selecionada ser Plani-
lhaEletrônica ou Documento.
Como implementação, muitas bibliotecas de software disponíveis comer-
cialmente, que o habilitam a construir interfaces janela/mouse/ícone, são es-
critas em linguagem orientada a objeto. Uma vez que uma janela
naturalmente tem muitas propriedades de um objeto, a maioria das ferramen-
tas de desenvolvimento para interfaces providas com janelas também tem
uma emenda de orientação a objeto que as percorre.
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 73

Assim, embora não seja muito verdadeiro que a orientação a objeto “per
si” torna os softwares mais utilizáveis, a realidade é que uma boa interface
gráfica do usuário torna o software mais utilizável e que a orientação a objeto
pode ser a melhor abordagem para a construção de bibliotecas de software que
suportem GUIs.

2.4.6 Gerenciamento de projetos de software


Até agora, direcionei quase tudo o que apresentei neste livro às pessoas téc-
nicas. Mas e o pobre gerente? A orientação a objeto é outra inovação técnica
que deve ser aceita em um sofrimento silencioso até que seus defensores se en-
vergonhem e entrem em outro local de trabalho? Ou pior: a orientação a objeto
é meramente outra panacéia com a qual um gerente pode acertar a si próprio
no pé?
Não. A orientação a objeto não é mais unicamente para os nerds. As van-
tagens técnicas da orientação a objeto também são vantagens técnicas para os
gerentes. Por exemplo, uma técnica que reduza a manutenção claramente per-
mite que os recursos de um gerente sejam alocados em provisões de aplicação
urgentes. Mas, para os gerentes, a orientação a objeto vai mais além do que
meramente o aspecto técnico. A orientação a objeto traz mudanças tanto para
a organização do local de trabalho como para o trabalho dos gerentes.
Quando um local de trabalho adota a orientação a objeto, sua organização
muda por diversas razões. A reutilização de classes vai requerer uma biblio-
teca de classes e um bibliotecário de classes. Cada programador migrará para
um dos seguintes grupos: o grupo que desenhará e codificará novas classes ou
o grupo que utilizará essas mesmas classes na criação de novas aplicações.
Com menos ênfase na programação (a reutilização implica menos código
novo), a análise de requisitos vai tornar-se relativamente mais importante.
Uma nova notação, tal como a UML, também terá um impacto. Muito
embora a análise de requisitos e o desenho de software sejam duas “coisas
abomináveis”, a UML é amplamente utilizada para modelá-las. Isso confere à
moderna OO (orientação a objeto) uma continuidade notacional que fazia mui-
ta falta às técnicas estruturadas de outrora. Eu pensei que essa “falta de
emendas” seria uma vantagem para todos envolvidos, até que encontrei um
gerente em um novo local de trabalho orientado a objeto que me disse o se-
guinte:
No passado, eu sabia que, quando eles estavam desenhando coi-
sas redondas [processos em um diagrama de fluxo de dados], es-
tavam fazendo análise, e, quando trocavam por coisas quadra-
74 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

das [módulos em um gráfico de estruturas], estavam mudando


para desenho. Hoje em dia, nunca sei o que eles estão fazendo
e me preocupa a idéia de que eles também não saibam!
Na condição de gerente cujo local de trabalho está mudando para um am-
biente orientado a objeto, você deverá estar ciente de tais mudanças organi-
zacionais. Você precisará treinar sua equipe de trabalho para suas novas
funções. E necessitará gerenciar as pessoas nessas funções. Deverá encorajar
a reutilização, mais do que a recodificação. Você precisará dar tempo às pes-
soas para que elas pensem em seus desenhos de classe de forma que as classes
assim construídas sejam adequadas para reutilização. Em breves palavras,
você precisará administrar equipes de trabalho que estarão repentinamente
utilizando diferentes terminologias e ferramentas, e um ciclo de vida distinto,
e — ao mesmo tempo — aspirando por objetivos que são novos, ou pelo menos
buscando um novo significado.
A orientação a objeto só funcionará bem se os gerentes a tratarem prefe-
rentemente como um meio e não como um fim. A orientação a objeto é o meio
para — escolha a alternativa que mais lhe interessar — a manutenção, a ex-
tensibilidade, a robustez, a distributividade, o suporte de GUIs, a redução no
tempo de entrega e assim por diante.
Na qualidade de gerente, você deve enfocar sua meta todo o tempo e uti-
lizar a orientação a objeto como uma tecnologia para atingir essa meta. Como
disse um gerente: “Quando compro sabão, sempre lembro que, após utilizá-lo,
minhas mãos estão realmente limpas”.
Se você não der um enfoque a sua meta, então a orientação a objeto, com
todos os seus custos (financeiros, organizacionais, sociais e emocionais), se pa-
recerá com um artefato de couro muito caro. Entretanto, se souber não apenas
o que está fazendo, mas também por que está fazendo, você extrairá a energia
para marcar o gol orientado a objeto que está buscando.

2.5 Resumo
Parte do encanto da orientação a objeto é a analogia entre os componentes de
software orientados a objeto e os circuitos eletrônicos integrados. Finalmente,
em software, temos a oportunidade de construir sistemas de modo similar ao
utilizado pelos engenheiros eletrônicos modernos: somos capazes de conectar
componentes pré-fabricados implementadores de abstrações poderosas. Mas,
para nos beneficiarmos disso, primeiro devemos identificar abstrações bem
fundamentadas de software e dispor de meios para interligá-las construtiva-
mente.
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 75

As técnicas para atingir com êxito esses “circuitos integrados de softwa-


re” se enquadram amplamente sob a rubrica da orientação a objeto. Orienta-
ção a objeto é um termo que agrupa as idéias de muitos pesquisadores de
software, desde os anos 60 aos dias de hoje. Entretanto, nem todo o mundo
concordaria com esse ponto de vista. Algumas pessoas (revolucionária e orien-
tadas a objeto) diriam que a orientação a objeto representa uma completa rup-
tura com o passado. Outras (reacionárias) tomariam uma posição contrária:
orientação a objeto representa, quando muito, uma aparência atraente, mas
superficial, de jargões sobre “a coisa antiga”.
A minha experiência diz que a orientação a objeto nem é a mesma coisa
antiga, nem uma completa ruptura com tudo o que algum dia aprendemos so-
bre software. Na verdade, é uma etapa revolucionária bem-vinda que vai além
na direção de enfrentar o desafio de desenvolver softwares ainda mais com-
plexos.
A orientação a objeto também direciona dois problemas de técnicas estru-
turadas. O primeiro é a fratura existente entre processos e dados e o descom-
passo entre o modelo de análise de requisitos e o modelo de desenho de
software. O segundo é a lacuna entre a assim denominada informação e as
abordagens de desenvolvimento de sistemas em tempo real. Em minha opi-
nião, uma boa abordagem orientada a objeto deveria transpor ambas as cate-
gorias de sistema.
A orientação a objeto faz com que ela mesma seja sentida em todas as
etapas do desenvolvimento de software. Em análise, ela demanda uma profun-
da investigação das abstrações dentro dos domínios dos usuários — pelo me-
nos, se deve ser atingida uma verdadeira reutilização. Em desenho, ela requer
uma organização robusta de construções de software, muitas das quais são
mais sofisticadas do que as encontradas no desenho estruturado convencional.
A construção de software confiável e robusto orientado a objeto requer o
estabelecimento de diversas asserções, tais como invariantes de classe, que po-
dem ser monitoradas por exceções em run-time. A manutenção de software é
aperfeiçoada não somente devido aos resultados da reutilização, “menos códi-
go deverá ser mantido”, mas também por causa que o software em si é cons-
truído com base em abstrações mais bem fundamentadas.
Embora a orientação a objeto não seja mais utilizada unicamente para
gráficos, ela é ainda valiosa para a construção de interfaces gráficas do usuário.
Por essa razão, as abstrações orientadas a objeto se adaptam bem às interfaces
naturais amigáveis, das quais muitos usuários esperam servir-se um dia.
76 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Finalmente, o desenvolvimento de software orientado a objeto será bem-


sucedido somente se for gerenciado de maneira inteligente. Os gerentes deve-
rão introduzir a orientação a objeto paulatinamente, e, depois, gerenciar suas
demandas (tal como a disciplina de engenharia de software) e resultados (tal
como a reutilização) de maneira bem cuidadosa.

2.6 Exercícios
1. A maioria das analogias se quebra em algum lugar. Ao ler os capítulos
deste livro, considere as imperfeições da analogia entre uma classe de ob-
jetos software e um circuito eletrônico integrado.
2. Tendo em mente o que você conhece sobre orientação a objeto, em que
classe você se colocaria: reacionário, evolucionista, revolucionário? Justi-
fique sua posição comparando desenvolvimentos em orientação a objeto
com os presentes na corrente predominante de desenvolvimento de soft-
ware.
3. Em sua opinião, é necessário que um local de trabalho escolha uma lin-
guagem (ou ambiente operacional) que tenha todas as propriedades
orientadas a objeto que descrevi no Capítulo 1? Em outras palavras, você
concorda com alguns revolucionários orientados a objeto que tendem a
zombar de locais de trabalho que optaram por algo que não seja a orien-
tação a objeto em sua forma mais plena?
4. Considere a “lista de celebridades” da orientação a objeto constante na se-
ção 2.1. Quem, na sua opinião, eu deixei de fora? Que contribuição essa
“celebridade” trouxe fizeram para o tema da orientação a objeto?

2.7 Respostas
1. Uma deficiência na analogia origina-se da forma como os ICs são interli-
gados por fios na maioria das linguagens orientadas a objeto atuais. Os
ICs eletrônicos permanecem simetricamente anônimos, uns em relação
aos outros. A “fiação” vai de um “plugue” de IC até um outro “plugue” de
IC e, em tempo algum, um IC “reconhece” os outros ICs com os quais ele
está conectado; ele “reconhece” apenas os seus próprios pinos, não os pi-
nos de outros ICs.
Isso não ocorre com classes e objetos. Classes são conectadas a outras
classes pelo nome explícito. Por exemplo, se a ClasseA é herdeira da Clas-
seB , então a ClasseA conterá uma linha de código tal como herda da
ClasseB. Um objeto envia uma mensagem nomeando um operador de ou-
CAP. 2 BREVE HISTÓRIA DA ORIENTAÇÃO A OBJETO 77

tro objeto. Isso é como se conectar um IC ao outro por uma solda de


chumbo, que sairia do interior de um e iria até o pino de outro circuito
integrado. Peter Wegner discute esse conceito na seção 6.13 de seu tra-
balho (Wegner, 1990). (No decorrer deste livro também abordo as cone-
xões entre classes mais detalhadamente.)
2. Se você for um reacionário orientado a objeto, então retorne para as abs-
trações-chave da orientação a objeto que abordei no Capítulo 1. Verifique
cada uma cuidadosamente para ver se você é capaz de achar semelhanças
com uma abstração similar em alguma publicação pré-orientada a objeto.
Se você for um revolucionário orientado a objeto, leia, por exemplo, o livro
de Yourdon e Constantine sobre desenho estruturado (Yourdon e Cons-
tantine, 1979). Você poderia dar um exemplo em que os conceitos mais
importantes desse trabalho (tais como acoplamento e coesão) são irrele-
vantes para nosso bravo e novo mundo orientado a objeto?
3. Em minha opinião, o debate sobre se “eu sou mais orientado a objeto do
que você” é um ponto discutível e dogmático, que tem pouco a ver com en-
genharia. O ponto da engenharia é: que benefícios da orientação a objeto
são mais importantes para que consigamos atingir nossos objetivos no lo-
cal de trabalho? Um ambiente parcialmente orientado a objeto apresenta
algumas das vantagens da engenharia de software da orientação a objeto,
mas faltam a ele outras. Portanto, um local de trabalho em que prevalece
o bom senso, compreende suas necessidades e opta por um ambiente que
satisfaça essas necessidades.
4. Ao decidir que outras pessoas acrescentem nomes à minha lista de cele-
bridades da orientação a objeto, você pode querer considerar os autores
de metodologias, linguagens ou ferramentas que utiliza em seu local de
trabalho. Uma possibilidade: pesquise a história inicial da linguagem
Java na Sun Microsystems.
Página em branco
“Veja só quão boa e prazerosa uma coisa é: irmãos,
vivam juntos em unidade!”

The 1662 Book of Common Prayer, 133:1

P arte II — the Unified Modeling


Language

PARTE II — THE UNIFIED MODELING LANGUAGE


Parte II — the Unified Modeling
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A Parte I explorou a orientação a objeto por meio de suas abstrações essen-


ciais e seu contexto em engenharia de software. A Parte II (Capítulos 3
a 7) apresenta uma notação gráfica para representar construções de desenho
orientado a objeto.
No final da década de 90, como mencionei no Capítulo 2, Grady Booch,
Ivar Jacobson e Jim Rumbaugh apareceram com a versão final da the Unified
Modeling Language — UML (Linguagem de Modelagem Unificada), que agora
é um padrão adotado pelos Object-Management Group — OMG (Grupo de Ge-
renciamento Dirigido a Objeto, entre outras organizações. A notação da UML
guarda certa semelhança com a da Object Modeling Technique — OMT (Téc-
nica de Modelamento a Objeto), que Rumbaugh e outros desenvolveram no
início dos anos 90. Como seria previsível, a UML também recebeu a influência
de Booch e de Jacobson, do ancestral triunvirato.
Eu parabenizo Booch, Jacobson e Rumbaugh (e seus numerosos colabo-
radores pelo mundo inteiro) pelo esplêndido esforço em remover o caos nota-
cional, terminológico e semântico que atormentou os primeiros e frenéticos
anos da orientação a objeto. Como bem sei, é muito difícil surgir com uma no-
tação para orientação a objeto que seja suficientemente simples de utilizar
mas poderosa o bastante para fazer jus a sua utilização.
Em 1990, Larry Constantine, Steve Weiss e eu desenvolvemos uma nota-
ção protótipo de desenho orientado a objeto. Ela foi denominada Uniform Ob-
ject Notation — UON (Notação Uniforme a Objeto) significando que ela
poderia ser utilizada “uniformemente” para retratar sistemas construídos com
técnicas estruturadas, técnicas orientadas a objeto ou uma mistura de ambas.

79

Parte II — The Unified Modeling


80 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Entretanto, a UON aplicava-se a apenas algumas das construções encontra-


das em sistemas orientados a objeto.1
Mais tarde, Brian Henderson-Sellers e Julian Edwards ampliaram a UON
além de suas origens iniciais em uma notação (EUON) que se tornou parte da
metodologia de seus MOSES.2 Trabalhando parcialmente com Henderson-Sel-
lers e Edwards, e parcialmente sozinho, desenvolvi a UON ainda mais, em uma
notação denominada Object-Oriented Design Notation — ou OODN (Notação de
Desenho Orientado a Objeto). Quando foram apresentadas, entretanto, nem a
UON, nem a OODN, jamais conseguiram empolgar as massas.
Contudo, aperfeiçoando a ainda primitiva EUON, Don Firesmith, Ian
Graham e Henderson-Sellers criaram uma esplêndida alternativa para a
UML. A notação deles é chamada OPEN Modeling Language (OML), e é parte
de uma abordagem ampla OPEN para expressar e desenvolver sistemas orien-
tados a objeto. Mas, apesar de seu rigor e metamodelo subjacente, a OML não
parece destinada a se difundir muito em aplicações.
Agora, vamos voltar à UML. Trata-se de uma linguagem com uma espe-
cificação semântica semiformal, que inclui sintaxe abstrata, regras bem-defi-
nidas e semântica dinâmica. A UML consegue capturar a estrutura de
sistemas orientados a objeto em um nível acima das linhas individuais de có-
digo, e pode ser expressa em diagramas que englobam a gama de construções
que aparecem em sistemas típicos orientados a objeto. Esses diagramas in-
cluem, por exemplo, o diagrama de classe (que retrata as construções de he-
rança, associação semântica, composição e agregação), o diagrama de
seqüência, o diagrama de colaboração e o diagrama de estado.
Uma boa notação de desenho deverá mostrar a estrutura do sistema como
um todo em uma forma gráfica e deixar definições semânticas detalhadas (de
algoritmos, invariantes de classe, precondições e pós-condições, por exemplo)
para o texto. Por essa razão, a UML não tenta substituir definições textuais
de classes e seus métodos. Em lugar disso, a linguagem provê uma estrutura
gráfica para a organização de construções de projeto e daí define cada cons-
trução por meio de um texto apropriado.
Antes de lançar-me na UML propriamente dita, permita-me relatar as metas
que Booch, Jacobson e Rumbaugh estabeleceram para si próprios ao desenvolve-
rem a UML. Você talvez queira mentalmente aferir o grau com que eles atingiram
esses objetivos à medida que revisarmos a UML nos capítulos seguintes.3

1. Veja Page-Jones e outros, 1990, para um relato da UON.


2. Veja Henderson-Sellers e Edwards, 1994, para a notação em MOSES.
3. Minha opinião: com apenas algumas exceções, eles fizeram um bom trabalho, porque é muito
difícil atingir todos esses objetivos (algumas vezes opostos). Para o estabelecimento mais re-
cente de objetivos, consulte www.rational.com.
PARTE II — THE UNIFIED MODELING LANGUAGE 81

As sete seguintes afirmações de objetivos foram extraídas dos próprios


autores principais da UML:

1. Prover aos usuários uma linguagem de modelagem visual


expressiva e pronta para uso, de forma que eles possam
desenvolver e intercambiar modelos significativos.
É importante que o padrão de análise e projeto OO suporte uma
linguagem de modelagem que possa ser utilizada “fora da caixa”
para a realização de tarefas normais de modelagem para uso geral.
Se o padrão simplesmente prover uma descrição-meta-meta
requerendo a customização de um conjunto particular de conceitos
de modelagem, então ele não atingirá a finalidade de permitir aos
usuários a troca de modelos isentos de informações perdidas ou
que não imponham um trabalho excessivo de mapeamento de
modelos para uma forma muito abstrata.
A UML consolida um conjunto de conceitos centrais de
modelagem, que são geralmente aceitos por muitos métodos e
ferramentas de modelagem da atualidade. Estes conceitos são
necessários em muitas, ou na maior parte das grandes aplicações,
muito embora nem todo conceito seja necessário em todas as partes
de todas as aplicações.
2. Prover mecanismos de extensibilidade e especialização
para ampliar os conceitos centrais.
Nós esperamos que a UML seja customizada à medida que novas
necessidades forem descobertas, e para domínios específicos. Ao
mesmo tempo, não desejamos forçar para que os conceitos centrais
comuns sejam redefinidos ou reimplementados para cada área
customizada. Portanto, acreditamos que os mecanismos para
extensão deveriam suportar desvios do caso comum, em vez de
serem solicitados para implementar os próprios conceitos centrais
de análise e projeto OO. Os conceitos centrais não deveriam ser
mudados mais do que o necessário.
Os usuários precisam saber:
1. construir modelos que utilizam conceitos centrais sem utilizar
mecanismos para extensão na maioria das aplicações normais;
2. acrescentar novos conceitos e notações para temas não
cobertos pelo núcleo;
82 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

3. escolher entre interpretações variantes de conceitos existen-


tes, quando não houver um claro consenso;
4. especializar os conceitos, as notações e as restrições, para
domínios de aplicações particulares.
3. Ser independente de linguagens de programação e proces-
sos de desenvolvimento particulares.
A UML deve e pode suportar todas as linguagens de programação
aceitáveis. Ela também deve e pode suportar vários métodos e
processos para construir modelos. A UML pode suportar lingua-
gens de programação múltiplas e métodos de desenvolvimento sem
uma dificuldade excessiva.
4. Prover uma base formal para entendimento da linguagem
de modelagem.
Pelo fato de os usuários servirem-se de formalidade para ajudá-los
no entendimento da linguagem, esta deverá ser precisa e acessível;
a inexistência de qualquer uma dessas dimensões prejudicará sua
utilidade. Os formalismos não devem requerer níveis excessivos de
procedimentos indiretos ou de sobreposição, e usos de notações
matemáticas de baixo nível, distantes do domínio de modelagem,
tais como a notação teórica e fixa, ou de definições operacionais
equivalentes a programar uma implementação.
A UML provê uma definição formal do formato estático do
modelo utilizando um metamodelo expresso em diagramas de classe
na UML. Essa é uma abordagem formal popular e amplamente
aceita para especificar o formato de um modelo e, diretamente,
conduzi-lo à implementação de formatos de intercâmbio.
5. Estimular o crescimento do mercado de ferramentas OO.
Ao capacitar vendedores para que defendam uma linguagem de
modelagem padrão utilizada pela maioria das pessoas e
ferramentas, a indústria se beneficia. Ao passo que os vendedores
ainda podem acrescer valor em suas implementações de
ferramentas, a habilitação da interoperabilidade é essencial. A
interoperabilidade determina que os modelos podem ser trocados
entre usuários e ferramentas sem a perda de informações. Isso
somente ocorreria se as ferramentas concordassem sobre o formato
e significado de todos os conceitos relevantes. Utilizar um nível-meta
maior não chega a ser uma solução, a menos que o mapeamento dos
conceitos em nível de usuário seja incluído no padrão.
PARTE II — THE UNIFIED MODELING LANGUAGE 83

6. Suportar conceitos de desenvolvimento de nível mais alto,


tais como colaborações, estruturas, modelos e componentes.
A semântica claramente definida desses conceitos é essencial para
colher o benefício total da OO e da reutilização. Defini-los dentro
do contexto holístico de uma linguagem de modelagem é uma
contribuição peculiar da UML.
7. Integrar as melhores práticas.
Uma motivação-chave por detrás do desenvolvimento da UML tem
sido a integração das melhores práticas na indústria, abrangendo
visões amplamente variáveis, baseadas em níveis de abstração,
domínios, arquiteturas, estágios de ciclos de vida, tecnologias de
implementação etc. A UML constitui, de fato, uma integração
dessas melhores práticas.

Se bem que os autores inicialmente não tenham identificado os quatro ob-


jetivos seguintes para a UML, eu acho que, por propósito ulterior ou por pura
sorte, de qualquer forma eles os atingiram com êxito. Assim, eu anexaria os
seguintes itens como objetivos realizados pelo próprio esforço pessoal deles:

1. Inclusão de uma UML mais simples, dentro de uma UML abran-


gente e geral
O desenhista médio não deseja confrontar uma “turba de símbolos pilha-
dores” a fim de expressar seus desenhos do dia-a-dia. Isso impele a UML
a s er pequena. Em contrapartida, uma notação abrangente como a
UML deve retratar construções a partir das diversas e mais importantes
linguagens orientadas a objeto, e deve também antecipar as necessidades
dos desenhistas que trabalham nos extremos esotéricos da OO. Isso im-
pele a UML a ser grande.
A UML acaba sendo tanto grande quanto pequena. Ela contém uma boa
variedade de construções de modelagem e a capacidade para que os usuá-
rios construam ainda mais. Por outro lado, descobri que utilizo talvez
apenas a metade da UML para representar a maioria de meus trabalhos
de desenho.
Assim, não se sinta obrigado a usar todo o conjunto da UML, somente
pelo fato de que ele está lá. Tudo bem se você escolher apenas os símbolos
que são relevantes a sua linguagem, seu ambiente e seu projeto. Há pos-
sibilidade de que você, como eu, descubra seu pequeno subconjunto roti-
neiro de símbolos úteis de UML para os projetos de seu local de trabalho.
84 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2. Utilidade em diversas perspectivas de modelagem


Uma perspectiva de modelagem (ou ponto de vista) determina as partes,
o nível de detalhes e as abstrações do tema que o modelo com essa pers-
pectiva pretende enfatizar. Em Designing Object Systems, por exemplo,
os autores Steve Cook e John Daniels assinalam três importantes pers-
pectivas de modelagem em um projeto orientado a objeto.4 Parafrasean-
do-os, pode-se afirmar que:

• A primeira é a perspectiva essencial, que modela conceitos a partir do


problema de negócio, sem referências à tecnologia de implementação.
• A segunda é a perspectiva de especificação, que modela as caracterís-
ticas do software que atenderão aos requisitos necessários para uma
solução de negócio.
• A terceira é a perspectiva de implementação, que modela a construção
do software.

Muito embora a princípio eu estivesse cético, verifiquei que a UML era


útil em todas essas três perspectivas de modelagem quando se utilizava
a mesma simbologia e terminologia de UML para cada perspectiva de
modelagem (se bem que muitos símbolos e termos possam prevalecer
mais em uma perspectiva que em outra). Uma expressão popular para
esse tipo de versatilidade é: “falta de emendas nos modelos”.
3. Correspondência com o código, suficientemente próxima para
permitir reengenharia
A UML retrata a estrutura do código orientado a objeto no nível exata-
mente acima do próprio código. Portanto, seria possível que as ferramen-
tas assumissem o código existente e, automática ou manualmente,
representassem sua saliente estrutura em UML.
4. Utilização assistida por computador, bem como manualmente
Já que as ferramentas de modelagem automatizadas (ou ferramentas
CASE) se tornaram comuns, uma notação orientada a objeto moderna
precisa atender às necessidades da tela, do mouse e da impressora. Mas,
apesar da disponibilidade de ferramentas de modelagem, uma grande
parcela da modelagem de sistemas se efetua em quadros brancos e nas
costas de envelopes. Felizmente, a maioria dos símbolos mais úteis da

4. Fowler e Scott, 1997, fizeram repercutir a noção de perspectivas de modelagem.


PARTE II — THE UNIFIED MODELING LANGUAGE 85

UML podem ser esboçados simplesmente com papel e lápis, anteriormen-


te à subseqüente e gloriosa reencarnação deles em um meio eletrônico.
Agora vamos recorrer ao layout da Parte II. Cada um dos cinco capítulos
introduzem diagramas e símbolos que cobrem um grupo particular de constru-
ções de desenho orientado a objeto.
O Capítulo 3 introduz o símbolo de classe (ou pin-out), que mostra uma
classe e seus atributos e operações, juntamente com suas assinaturas formais.
O Capítulo 4 apresenta o diagrama de classe, que mostra hierarquias de
herança simples ou múltipla de classes e outras interligações de classe por
meio de construções de associação, composição e agregação. As construções de
composição e agregação são tipicamente utilizadas para retratar os objetos
que, como um grupo, compreendem um objeto de uma dada classe. A associa-
ção é utilizada para capturar os relacionamentos semânticos (ou orientados a
negócios) entre os objetos de várias classes.
O Capítulo 5 cobre diagramas de seqüência e colaboração, ambos os quais
mostram (de formas diferentes) interações em run-time entre objetos. O pri-
meiro diagrama, apresentando um firme formato, enfatiza fortemente o ti-
ming e a seqüência de mensagens ao mostrar operações de objetos
grosseiramente representadas, em escalas, nos seus períodos de execução.
Esse diagrama é especialmente útil para desenhar sistemas com execução e
envio assíncrono de mensagens concorrentes. O último diagrama, apresen-
tando um formato mais livre, enfatiza as mensagens (e argumentos reais) pas-
sados entre objetos em run-time.
O Capítulo 6 é dedicado ao diagrama de estado, que mostra como os ob-
jetos fazem transições de estado a estado dentro do espaço de estado definido
para a classe deles. Muitas das questões no Capítulo 6 referem-se à adaptação
dos diagramas de transição de estado para o desenho orientado a objeto.
O Capítulo 7 encerra a Parte II com mais três diagramas especializados.
O primeiro é o diagrama de pacote, que mostra o agrupamento de elementos
de software em estruturas de encapsulamento em um nível maior do que o da
classe individual. O segundo é o diagrama de distribuição, que representa a
arquitetura global do sistema, mostrando unidades de tecnologia e seus vín-
culos (links) de comunicação físicos. Ele também retrata como um sistema de
software é dividido em compartimento ao longo dos vários pedaços do hardwa-
re subjacente e da tecnologia do software. Corresponde ao diagrama de nave-
gação janela, que revela os caminhos significativos da aplicação entre janelas
em uma interface gráfica do usuário.
86 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Eu não pretendo que este livro seja um manual de referência para cada
símbolo e nuança da UML.5 Portanto, uma vez que aproximadamente 40% da
UML provavelmente expressará cerca de 98% de seus resultados (delivera-
bles) de desenho, concentrei-me na porção da UML que rende o maior número
de desenhos por símbolo. Entretanto, certamente apresento uma quantia su-
ficiente de UML para que você entenda os diagramas na Parte III do livro.
Ao selecionar a porção da UML para apresentar neste livro, omiti os dia-
gramas de caso de uso e atividade. Os diagramas de caso de uso são extrema-
mente valiosos durante a análise de requisitos e são importantes o bastante
para garantir um livro inteiro sobre eles.6 Todavia, não são centrais aos prin-
cípios do desenho orientado a objeto que determinei neste livro. Quanto aos
diagramas de atividade, considero-os excessivamente poluídos graficamente
para exprimirem as idéias de desenho que desejo aqui comunicar.
Ocasionalmente, assinalo uma variação menos importante na UML, que
talvez você prefira quando estiver desenhando uma particular construção de
desenho em um quadro branco. O estilo de notação que você escolherá vai de-
pender de seu gosto e de sua técnica de desenho. Entretanto, já que aposto
que você utilizará uma ferramenta de modelagem automatizada para criar a
maioria de seus resultados formais, você provavelmente terá de trabalhar den-
tro do dialeto notacional dessa ferramenta a fim de obter em papel seus dia-
gramas de desenho. Incidentalmente, do que vi até agora, as ferramentas de
modelagem em UML extraviam-se bem menos do reto e estreito em suas ico-
nografias, em relação ao que as ferramentas para outras notações fazem; por-
tanto, na prática, você não ficará assoberbado com confusas mutações de
notação.

5. Como um manual de referência completo de UML, sugiro em Rumbaugh e outros, 1999, e


quaisquer subseqüentes atualizações publicadas pelos mesmos. Diversos outros livros tam-
bém foram dedicados à UML. Dois excelentes livros para sua pesquisa futura são Fowler e
Scott, 1997, e Muller, 1997.
6. Veja, por exemplo, Rosenberg e Scott, 1999.
E xpressão básica para classes,
atributos e operações
EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES
3.Expressão Básica Para Classes, Atributos e Operações
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

N este capítulo, descrevo a notação da UML para classes, e depois exploro


essa mesma notação para as propriedades (atributos e operações) que po-
dem ser lotadas para uma classe. Eu finalizo este capítulo com a aplicação da
UML para duas variações encontradas na classe básica: o pacote de utilidade
e a classe parametrizada.
Iniciemos com a notação fundamental para classes.

3.1 A Classe
O símbolo de uma classe, no lado esquerdo da Figura 3.1, é central a qualquer
aplicação de UML. O mais alto dos três compartimentos do símbolo mostra o
nome da classe; ele inicia, convencionalmente, com uma letra maiúscula. Este,
em particular, foi imaginativamente denominado de AlgumaClasse. Dentro
desse compartimento, você também pode acrescentar alguma informação adi-
cional sobre a classe, conforme discutido na seção 3.7. A convenção para as fer-
ramentas de modelagem é representar o nome da classe em fonte do tipo
negrito, mas eu nunca consegui escrever em negrito sobre um quadro branco.

Figura 3.1 Símbolo de uma classe na forma plena e na reduzida.

87
88 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O compartimento do meio exibe os atributos da classe, e o compartimento


inferior exibe suas operações. Retornaremos a esses dois compartimentos na
próxima seção. Podem ser acrescentados compartimentos extras neste padrão
constituído de três compartimentos, e estes podem receber nomes definidos
pelo usuário (tais como invariantes ou exceções), expressando seus propósitos.
Um símbolo alternativo para uma classe aparece no lado direito da Figu-
ra 3.1. Esse símbolo reduzido (que omite os compartimentos destinados ao
atributo e à operação) é conveniente quando se deseja exibir somente a classe
e o nome dela.
A Figura 3.2 mostra os símbolos equivalentes representativos de um ob-
jeto. Conforme vimos no Capítulo 1, quando um objeto é gerado a partir de
uma classe, ele adquire uma estrutura que é praticamente idêntica à dessa
classe.1 É bem natural, portanto, que a UML utilize para um objeto o símbolo
da sua classe.

Figura 3.2 Símbolo de um objeto (instância de uma classe)


na forma plena e na reduzida.

As diferenças mais dignas de nota encontradas na notação entre um ob-


jeto e sua classe são evidentes no compartimento de nomes. Por exemplo, em
contraste ao estilo utilizado para um nome de classe, um nome de objeto é su-
blinhado, e não é escrito em negrito. Essas diferenças tipográficas capacitam
o seu olho a diferenciar rapidamente entre classes e objetos. Além do mais, a
sintaxe de um nome de objeto assume a forma de NomeDaInstância: NomeDaClas-
se. Como assinalei no Capítulo 1, um objeto na realidade não tem um nome
próprio — somente um identificador. Todavia, na maioria dos contextos de de-
senho, tal como quando um objeto está enviando uma mensagem para outro,
os objetos são conhecidos por algum tipo de nome (por exemplo, estaContaDe-

1. A principal diferença é que um objeto — uma instância de uma classe — não tem quaisquer
atributos ou operações de classe. Entretanto, as classes típicas têm muito poucos atributos e
operações de classe. A maioria dos atributos de uma classe típica são atributos de instância
(ou seja, atributos de objetos da classe), e a maioria das operações são operações de instância
(ou seja, operações de objetos da classe).
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 89

Cliente ou AsaEsquerda). Se você não tiver um bom nome para objeto — o que
é bem possível em seu primeiro trabalho de desenho — pode deixá-lo como
anônimo. Nesse caso, uma notação satisfatória seria: NomeDaClasse.

3.2 Atributos
Aqui temos uma rápida revisão dos atributos antes de explorarmos suas re-
presentações na UML. (Para fins de clareza, limitarei minha discussão a atri-
butos de instância publicamente visíveis.)

• Um atributo representa informações sobre um objeto.


• O termo atributo não é bem um sinônimo de variável. O atributo re-
presenta uma propriedade definida de forma abstrata, independente-
mente de como essa propriedade é implementada no seu interior. A
variável, em contrapartida, é um mecanismo de implementação interno.
Apesar de tudo, para cada dez atributos, oito acabam sendo imple-
mentados como uma simples variável, idêntica em natureza ao atri-
buto em si. Por exemplo, o atributo comprimento pode ser
implementado com uma variável comprimento.
Na nona ocasião, entretanto, pode estar envolvido algum tipo de manipu-
lação. Por exemplo, o atributo data pode ser transposto para dentro e fora de
um objeto na forma DD/MM/AAAA, ao passo que pode ser armazenado em uma
variável data sob a forma de números inteiros (diga-
mos, o número de dias desde o nascimento do rei Grog,
o Magnânimo).
Na décima ocasião, a derivação de um valor de
atributo pode envolver diversas variáveis. Por exem-
plo, o atributo capacidade de um cubóide (paralelepípe-
do retangular) é presumivelmente calculado
multiplicando-se os valores do comprimento, largura e
altura do Cubóide. Veja à direita a ilustração útil de um
cubóide.

• Em geral, um atributo é tanto acessível quanto determinável desde o


lado externo do objeto.
90 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• Por outro lado, alguns atributos são do tipo apenas para leitura (read-
only), isto é, acessíveis apenas desde o exterior do objeto.2 Em geral
— mas não necessariamente — esses atributos são derivados de ou-
tros; por exemplo, capacidade de um objeto Cubóide ou idade de uma
Pessoa (derivada de dataDeNascimento).
Note que, muito embora capacidade constitua um atributo apenas para lei-
tura, ela não é uma constante; ela pode ser alterada realizando-se certas opera-
ções no objeto; por exemplo, por meio de escala (que amplia ou reduz um cubóide).
Agora vamos ver como a UML representa atributos. A classe Pessoa na
Figura 3.3 tem três atributos listados no seu compartimento médio: nome, da-
taDeNascimento e idade. Cada nome de atributo é seguido por sua respectiva
classe (ou tipo de dado), após um sinal de dois pontos (:). Eu precedo os atri-
butos apenas para leitura com um sinal de corte (slash) inclinado para frente
(/); por exemplo, / idade.3 A classe Cubóide na Figura 3.3 tem quatro atributos:
comprimento, largura, altura e capacidade, o último dos quais não é diretamen-
te determinável (em outras palavras, é apenas para leitura).

Figura 3.3 Atributos.

2. Isso corresponde à noção, na linguagem de definição de interface (interface-definition language


— IDL) da CORBA, do termo apenas para leitura (read-only). Eu suponho que um atributo po-
deria ser determinável, mas não acessível, a partir do lado externo do objeto. Todavia, tenho vis-
to muitos desses atributos e não acho que a UML tenha qualquer notação para essa situação.
3. Na UML, o sinal de corte inclinado para a direita (/) formalmente significa um atributo deri-
vado. Entretanto, acho que o fato de um atributo ser derivado ou não é uma decisão de im-
plementação. Externamente, deveríamos apenas ver se um atributo é diretamente
determinável ou não; para essa finalidade acho o sinal (/) conciso e informativo. Embora esse
sinal signifique “não diretamente determinável” neste livro, você e sua equipe de trabalho de-
veriam decidir como usá-lo em seus projetos.
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 91

3.3 Operações
Nesta seção, analisamos as operações e exploramos suas representações na
UML ao mesmo tempo. Conforme mostrado na Figura 3.4, as operações apa-
recem no compartimento mais baixo com suas assinaturas formais completas.
Cada assinatura formal compreende o nome da operação, juntamente com a
lista dos argumentos de dados de entrada e saída formais da operação4

Figura 3.4 Operações.

O padrão UML pede pelas palavras-chave in e out antes de cada argu-


mento para mostrar seu sentido — para dentro ou fora da operação (e inout
indica um argumento que tem ambos os sentidos, in e out). Neste livro, desig-
no os argumentos de entrada como o padrão (default), omitindo portanto a pa-
lavra-chave in. Onde aparecerem argumentos de saída, utilizo a palavra-chave
out, seguida por um ou mais argumentos. (Não utilizo argumentos inout.)

• Um atributo típico requer duas operações padrões: uma operação ob-


ter (get) e uma operação especificar (set).5 Conforme mostrado na Fi-
gura 3.4, a classe Pessoa tem uma operação obterDataDeNascimento,

4. Argumento formal é um termo tradicional em informática para um argumento que aparece


na definição de uma função (geralmente no título da função, que define a interface da mes-
ma). Em contraste, o argumento real é fornecido por um chamador (caller) da função. Em
orientação a objeto, a mesma distinção entre formal e real aplica-se aos argumentos de ope-
rações.
5. A operação obter (g et) é um exemplo de uma operação consulta (ou de acesso), cuja execução
deixa o estado do sistema inalterado. A operação especificar (set) é um exemplo de uma ope-
ração não-consulta (ou modificadora), cuja execução pode mudar o estado do sistema.
92 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

que retorna o valor da data de nascimento da pessoa para quem quer


que necessite da mesma, e uma operação especificarDataDeNascimento,
que define uma data de nascimento para uma pessoa. De maneira si-
milar, Cubóide tem as operações obterComprimento e especificarCom-
primento, entre outras.
• Obviamente, um atributo que seja apenas para leitura não precisa de
uma operação especificar. Nesse caso, por exemplo, a classe Pessoa
tem nela definida apenas uma operação correspondente ao atributo
idade, chamada obterIdade, e Cubóide tem obterCapacidade, mas não
especificarCapacidade.
• Poucos atributos requerem operações que necessitam de argumentos
de entrada de dados. Esses atributos freqüentemente detêm dados em
sua história prévia. Por exemplo, o atributo altura, definido em uma
Pessoa, varia com o tempo. Se a aplicação mantiver a história desse
atributo, em decorrência a operação obterAltura tomará data como um
argumento de entrada e retornará a altura da pessoa naquela data.
A assinatura formal dessa operação seria obterAltura (data: Data, out
altura: Comprimento). O método implementador dessa operação pode
utilizar uma tabela de progressão (table lookup) para obter a altura
ou pode calculá-la de forma diferente, mas esse detalhe fica escondido
de nós nesse ponto.
• Algumas operações exigem comunicação com outros objetos, por meio
da passagem de mensagens. Por exemplo, para alterar o emprego de
uma Pessoa, você necessitaria de uma operação tal como alterarEmpre-
go (novoEmprego: Emprego, dataEfetiva: Data). Isso resultaria em uma
considerável comunicação com objetos relacionados a funcionários (da
classe Organização e assim por diante). Examinaremos esse tipo de co-
municação da UML no Capítulo 5.

Os nomes de operação obterDataDeNascimento e especificarDataDeNasci-


mento, mencionados anteriormente, seguem uma convenção usual de atribui-
ção de nome para as operações obter e especificar de um atributo típico
dataDeNascimento. Entretanto, existem convenções alternativas para as ope-
rações de atribuição de nome e de descrição na UML. Por exemplo, alguns lo-
cais de trabalho iriam preferir chamar a primeira operação simplesmente de
dataDeNascimento, especialmente se pretendessem programá-la como uma
função em vez de procedimento. Portanto, em vez de uma mensagem como
pessoa1.obterDataDeNascimento (out dataDeNascimento), eles teriam uma men-
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 93

sagem com melhor aparência, pessoa1.dataDeNascimento. Mostro essa conven-


ção na Figura 3.5.

Figura 3.5 Atributos com uma convenção de atribuição


de nome funcional para operações obter (get).

Observe que sob essa convenção de atribuição de nome, a maioria das


operações obter na forma é idêntica a seus atributos correspondentes (embora
a operação altura da classe Pessoa ainda necessite do argumento de entrada
data). Isso faz com que muitos locais de trabalho omitam todas as operações
básicas e enfadonhas, obter e especificar, de seus símbolos de classe na UML.
Como exemplo, veja a Figura 3.6.
Certamente que as operações obter e especificar ainda estão lá, implicita-
mente, e terão de ser programadas. Mas, pelo fato de não exibi-las, os mode-
ladores conseguem reduzir significativamente a desordem e o tamanho dos
símbolos de classe da UML. Essa é a convenção que adoto para o restante des-
te livro, exceto onde eu queira realçar uma operação obter ou especificar lis-
tando-a no compartimento inferior de um símbolo de classe. Quando eu
realmente desejar exibir uma operação obter, vou nomeá-la com a convenção
de atribuição de nome funcional: ou seja, usar o nome preço e não a obterPreço.
Alguns acadêmicos descrevem o símbolo de classe, com suas operações,
como um diagrama de definição em ADT (ADT — definition diagram), no qual
ADT representa “tipo de dado abstrato” (“abstract data-type“). Mas outras pes-
soas apelidaram-no de pin-out, porque ele se assemelha ao diagrama de pinos
de IC em um catálogo de circuito integrado.
94 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 3.6 Símbolos de classe com as operações básicas,


obter e especificar, omitidas.

Para reforçar a analogia entre uma classe e um circuito integrado, apre-


sento (na Figura 3.7) um exemplo de um diagrama pin-out para um circuito
integrado eletrônico, um regulador fictício. O diagrama exibe o que cada um
dos oito pinos requer como entrada ou provê como saída. O comportamento de
alguns dos pinos é também sugerido por seus nomes. Os pinos do circuito in-
tegrado são similares, é certo, a operações de classe.

Figura 3.7 Diagrama pin-out para circuito integrado de um regulador fictício.

3.4 Sobreposição de Operações


As operações sobrepostas aparecerão muitas vezes no diagrama de classe,
cada hora com uma assinatura diferente (isto é, com um número diferente de
argumentos ou com diferentes classes de argumentos)6. Na Figura 3.8, por

6. Você deve recordar que, no Capítulo 1, afirmou-se que uma operação é sobreposta se ela for
definida por mais de uma vez na mesma classe.
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 95

exemplo, a operação rebaixarPreço da classe LinhaDeProdutosParaVenda, que re-


duz o preço de um item, pode ter duas versões. A primeira versão de rebaixar-
Preço suporta um argumento de entrada, definido como porcentagem de
desconto. A segunda versão de rebaixarPreço não suporta argumentos de en-
trada; ela sempre reduz o preço seguindo a quantidade (default).

Figura 3.8 Dois pares de operações sobrepostas.

Há uma segunda operação sobreposta, a operação obter totalDeUnidades-


Vendidas. A primeira versão toma o argumento de entrada data e retorna com
o número total de unidades vendidas da linha de produtos desde o Big Bang.
(Incidentalmente, a operação registroDeVenda provavelmente atualiza uma va-
riável interna que faz lembrar quantas unidades foram vendidas em determi-
nada data.)

3.5 Visibilidade de Atributos e Operações


A UML incorpora um prefixo a um nome de atributo ou nome de operação
para indicar a visibilidade da propriedade. Conforme mostra a Figura 3.9, os
atributos ou operações públicas são prefixadas com um sinal de adição (+). Os
atributos ou operações protegidas são prefixadas com um símbolo de número
(#), e os atributos ou operações privadas com um sinal de subtração (−). En-
tretanto, uma vez que normalmente se deseja um diagrama de classe para
mostrar somente atributos e operações públicas, a maioria dos desenhistas (e
grande parte das ferramentas de modelagem) omite o sinal de adição, exceto
quando quer dar ênfase especial ao mesmo.
96 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 3.9 Atributos e operações, públicas, protegidas e privadas.

Embora o que chamamos público, protegido ou privado dependa de qual


linguagem é utilizada, é possível estabelecer algumas definições válidas em
diversas linguagens.7 Por exemplo, vamos descrever uma característica c, um
atributo ou uma operação definida para um objeto obj da classe C.

• Se c for pública, então c será visível para qualquer objeto, e c será


herdada pelas subclasses de C.
• Se c for privada, então c será visível só para o objeto obj. Alternati-
vamente, como definido em C++ e Java, se c for privada, então c será
visível apenas para os objetos da classe C — em outras palavras, para
o obj e sua prole; nesse caso c não será herdada pelas subclasses de
C.
• Se c for protegida, então c será visível só para os objetos da classe C
e para os objetos das subclasses de C; neste caso c será herdada pelas
subclasses de C.

Entretanto, no final das contas, os desenhistas utilizarão os prefixos da


UML, +, − ou #, que expressarem da melhor forma a definição de visibilidade
suportada pelas suas linguagens de programação.

3.6 Atributos e Operações de Classe


Na Figura 3.10, os nomes sublinhados indicam atributos e operações de clas-
se. Isso dado, você poderá perguntar: “Ei, por que a UML utiliza o sublinhado
para retratar propriedades de classe? Não foi você que disse anteriormente
que o sublinhado significa instâncias?”

7. Para uma discussão divertida de quão exatamente diversa a definição de “protegida” pode ser,
veja Fowler e Scott, 1997, p. 100-101.
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 97

Figura 3.10 Atributo de classe público e operação de classe privada.

Eu não ficaria contente se você me fizesse essa pergunta. Eu poderia me


sair com uma desculpa como “eu não faço as notícias, apenas as reporto”. To-
davia, existe uma razão para que atributos e operações de classes recebam o
sublinhado — se bem que é uma razão muito esquisita. Se você for uma pes-
soa crente, poderá aceitar a notação pelo significado manifesto, pular para a
próxima seção e ter um dia feliz e descontraído. Do contrário, apegue-se fir-
memente na racionalização oficial dessa notação.
O ambiente operacional orientado a objeto vê uma única classe (digamos,
C) como uma instância da classe denominada Classe, da qual cada classe é
uma instância. Portanto, sob a perspectiva do ambiente, cada propriedade de
classe de C existe como instância e deve ser sublinhada.
Eu sei que é uma interpretação um tanto forçada. Talvez fosse melhor
você fazer o que os crentes fazem (eu inclusive): esqueça a explicação e lem-
bre-se apenas da notação. Ou, quando estiver modelando em um quadro bran-
co, coloque prefixos em nomes de atributos e operações de classe com o símbolo
do cifrão ($). Esta é a antiga convenção da UML, que por sinal ainda considero
bem conveniente.

3.7 Operações e Classes Abstratas


Uma operação é abstrata se ela é isenta de implementação (isto é equivalente
a uma função virtual pura baseada em classe em C++, ou uma característica
diferida em Eiffel). Em outras palavras, ela tem uma interface e uma funcio-
nalidade definida, mas nenhum método de implementação com código real!
Uma classe abstrata (ou classe diferida, em Eiffel) não gera objetos, por-
que geralmente ela tem, no mínimo, uma operação abstrata nela definida. Se
ela na verdade criasse um objeto, uma mensagem invocando a operação abs-
trata do objeto provocaria um erro de run-time. Sendo assim, a mensagem se-
guinte é ilegal em uma classe abstrata:
98 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

AlgumaClasseAbstrata.Nova; // código ilegal!8

Em face disso, uma operação abstrata, e a classe na qual ela está defini-
da, podem parecer inúteis. Assim, por que as temos em uma linguagem orien-
tada a objeto?
A resposta tem relação com a herança. Uma operação abstrata em uma
classe C serve como um modelo, indicando para o leitor que classes abaixo de
C, na hierarquia de herança, proporcionarão uma implementação concreta da
operação (também conhecida como implementação não abstrata ou efetiva).
Em última análise, cada uma das subclasses mais inferiores irão provavel-
mente aplicar seu próprio método para implementar a operação, provavelmen-
te otimizada para atender as necessidades específicas dessa subclasse.
Por exemplo, utilizando a classe Polígono do Capítulo 1, poderíamos
transformar obterÁrea em uma operação abstrata de Polígono, que, em decor-
rência, se tornaria uma classe abstrata. Essa transformação teria duas impli-
cações:

1. Seríamos obrigados a prover uma operação concreta obterÁrea para Hexá-


gono. (Nós já temos operações concretas para Triângulo e Retângulo.)
2. Jamais geraríamos um objeto da classe Polígono. Ou seja, nunca escreve-
ríamos Polígono.Novo, porque uma mensagem para sua operação obterÁrea
causaria um erro. Deveríamos gerar um objeto somente de uma das clas-
ses Triângulo, Retângulo ou Hexágono. (Na verdade, essa diretriz é impos-
ta como regra em Eiffel, Java e C++.) Entretanto, ainda poderíamos ter
escrito var p: Polígono. Essa assertiva (na linguagem de pseudocódigo da
OO que utilizo neste livro) significa que p pode (polimorficamente) reter
o identificador de um objeto de qualquer uma das classes, Triângulo, Re-
tângulo ou Hexágono.

A Figura 3.11 mostra Polígono como exemplo de uma classe abstrata, com
o nome da classe abstrata em itálico, e a propriedade {abstrata} entre chaves
sob o nome. Algumas ferramentas de modelagem abandonam o nome da pro-
priedade e deixam a fonte em itálico realizar todo o trabalho. Isso é suficiente
na grande maioria dos casos, mas acredito que o itálico é um tanto duro para
ser tratado em um quadro branco. A operação obter abstrata, obterÁrea, tam-

8. No Exercício 4 do Capítulo 1, vimos que a geração a partir das classes literais (tal como
NúmerosInteiros.Novo) é ilegal na maioria das linguagens. Quanto a esse caso, a classe
abstrata é similar à classe de literais. Porém, em outras questões, ela possui um conceito
distinto. Uma classe abstrata não possui objetos, mas uma classe literal pode ter muitos
objetos predefinidos.
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 99

bém aparece em itálico, com a propriedade {abstrata} entre chaves após seu
nome.

Figura 3.11 Classe abstrata com uma operação abstrata, obterÁrea.

3.8 O Utilitário
Utilitário (ou pacote de utilitário) é um grupo de procedimentos e funções en-
capsulados em um única unidade com um conjunto de dados privados. Ele di-
fere da classe pelo fato de que os objetos individuais nunca são gerados a
partir dele; utilitário se assemelha muito mais a um grupo de funções e pro-
cedimentos convencionais (como uma biblioteca dinamicamente concatenada)
ou um pacote de Ada-839.
Utilitário é como uma classe destituída de objetos: suas operações são, na
realidade, operações de classe. Alternativamente, a utilidade poderia ser con-
siderada uma classe com um único objeto predefinido.
A Figura 3.12 mostra a notação da UML para um utilitário. O nome da
classe é precedido pelo estereótipo «utilitário», encerrado no que meus amigos
da editora disseram-me eram guillemets. Repare que nenhum dos nomes de
operação na Figura 3.12 está sublinhado. Muito embora as operações de uma
utilidade sejam, na realidade, operações de classe, por convenção a UML não
sublinha seus nomes.

9. Eu não utilizo o termo pacote aqui, porque a UML utiliza pacote para um conceito diferente
(mas relacionado). Em Ada, você pode criar novos pacotes a partir de um pacote genérico, uti-
lizando-se da palavra-chave novo. Entretanto, isso não é completamente análogo à geração
de objetos. A habitual implementação do COBOL para um utilitário é um subprograma com
múltiplos e discretos pontos de entrada. Um sistema desenhado sobre utilitários, em vez de
classes, é geralmente denominado baseado em objeto (object-based). Em desenho estruturado,
o termo que designa um utilitário é agrupamento de informações (information cluster) ou mó-
dulo baseado em informações (information-strenght module).
100 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 3.12 Utilitário para tratar de uma tabela de símbolos.

O utilitário é valioso para a implementação de construções de software


com apenas uma única instância, tal como um pacote matemático ou um
daemon. (O daemon é um software que opera como monitor em condições va-
riáveis. Por exemplo, um daemon pode vigiar entradas de dados em sistemas
para a ocorrência de eventos no ambiente; outro tipo de daemon é aquele que
pode vigiar um banco de dados para que certa condição se torne verdadeira.)
O utilitário é útil também por conseguir prover uma “camada superficial”
de orientação a objeto para programas escritos em linguagens convencionais,
tais como C ou COBOL. Um utilitário bem-definido construído em torno de al-
gum antigo e venerável COBOL, ou de outro código tradicional, é comumente
denominado wrapper.

3.9 Classes Parametrizadas


A UML exibe uma classe parametrizada (também conhecida como classe ge-
nérica ou, em C++, classe padrão) com uma caixa pontilhada sobre o topo di-
reito do símbolo de classe padrão. Dentro dessa caixa vai a lista dos
argumentos de classe formais (dos quais normalmente há somente um). Veja
a Figura 3.13.

Figura 3.13 Classe parametrizada, com um argumento de classe formal.


Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 101

A Figura 3.13 mostra a classe Conjunto, uma típica classe container, que
toma uma classe formalmente denominada T como argumento. Quando uma
classe real, assim como Carro, é fornecida a T (ou, expondo mais formalmente,
“vinculada a T”), cada um dos objetos daquela classe representará um conjunto
de carros. O nome da classe parametrizada, vinculada, é então dado como Con-
junto <Carro>, o qual é mostrado no lado esquerdo da Figura 3.14.

Figura 3.14 Classe vinculada, formada a partir de uma classe parametrizada.

No lado direito da Figura 3.14, mostro a classe Frota, que é simplesmente


um sinônimo mais fácil de decorar de Conjunto <Carro>. (O sinal de igual é
meu símbolo para a sinonímia.)
A Figura 3.15 mostra a classe Frota novamente, desta vez em um diagra-
ma de UML mais sofisticado. Aqui, vemos o vínculo específico do argumento
de classe real, <Carro>., com o argumento de classe formal do Conjunto, T. A
seta tracejada, apontando para cima, representa explícita e graficamente o
vínculo (indicado pelo estereótipo «vínculo»).

Figura 3.15 Classe vinculada, formada a partir de uma


classe parametrizada (outra representação).
102 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

3.10 Resumo
O símbolo de classe é o fundamento da UML. Ele é tipicamente mostrado com
três compartimentos; do topo à base, eles contêm o nome de classe e algumas
informações suplementares, — respectivamente, atributos e operações. Os ob-
jetos aparecem em uma forma similar às classes, exceto pelo fato de que o
nome de objeto, no compartimento superior, é sublinhado e normalmente ex-
presso como obj: Classe.
O atributo representa uma informação sobre um objeto e aparece no com-
partimento médio do símbolo de classe: cada atributo é listado pelo nome, se-
guido pela sua classe (ou tipo de dado [data-type]). Por implicação, quando um
atributo é listado, o compartimento de operação contém uma operação obter
(get) e uma operação especificar (set). Se a operação especificar não for neces-
sária, ou não for permitida — por exemplo, se o valor do atributo é derivado
de um algoritmo —, precedo o nome do atributo com um sinal de corte (slash)
inclinado para a direita (/).
No terceiro compartimento do símbolo de classe, cada operação é listada
com sua assinatura formal plena (seu nome, seguido pelos argumentos for-
mais de entrada e/ou saída de dados que uma mensagem deve fornecer para
invocar a operação). Convencionalmente, as operações obter e especificar são
omitidas por brevidade, a menos que um desenhista necessite enfatizar uma
operação — tal como uma operação obter requerendo um argumento de entrada.
Muitas operações executam atividades mais complexas do que simples-
mente especificar ou obter valores para atributos — atividades essas que pos-
sivelmente exigem comunicação com outros objetos ou dispositivos externos. A
implementação dessas operações recebe o nome de método de operação.
Os nomes dos atributos e das operações públicas, protegidas e privadas
são prefixados, respectivamente, por um sinal de adição (+), um símbolo de
número (#) e um sinal de subtração (−). Entretanto, o sinal de adição é nor-
malmente omitido porque a visibilidade é pública por padrão (default). Aos
atributos e operações de classe que não forem gerados para objetos indivi-
duais, são dados nomes sublinhados. Uma operação é sobreposta se ela apa-
rece diversas vezes em um diagrama de classe, cada hora com uma assinatura
diferente (com um número diferente de argumentos ou com diferentes classes
de argumentos).
O nome de uma classe abstrata aparece em itálico, e é seguido pela pro-
priedade {abstrata}. O nome de uma operação abstrata também aparece em
itálico e tem a propriedade {abstrata}. O nome de uma classe de utilitário é
precedida pelo estereótipo «utilitário». Embora as operações de um utilitário
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 103

sejam, na realidade, operações de classe, por convenção os seus nomes não são
sublinhados.
Uma classe parametrizada (ou classe genérica, ou padrão) aparece com
uma caixa pontilhada em cima do canto direito superior. A caixa pontilhada
contém a lista de nomes dos argumentos formais, cada um dos quais repre-
senta uma classe específica a ser fornecida (ou “vinculada”) à classe parame-
trizada. Uma classe vinculada, que corresponde a uma classe parametrizada
já vinculada com classes específicas, é denominada de acordo com a seguinte
sintaxe:

ClasseParametrizada <ClasseFornecida1, ClasseFornecida2,


ClasseFornecida3>

Alternativamente, pode ser dado um nome significativo à classe vinculada:

NomeSignificativo = ClasseParametrizada <ClasseFornecida1,


ClasseFornecida2, ClasseFornecida3>

Em uma forma notacional, o vínculo das classes específicas é mostrado


por uma seta desde a classe vinculada até a classe parametrizada. A seta é
marcada com o estereótipo «vínculo» e com a lista das classes que foram pro-
vidas.

3.11 Exercícios
1. Resuma as diferenciações entre classes (tal como abstrata versus concre-
ta), que abordamos neste capítulo e no Capítulo 1.
2. Resuma as características diferenciadoras de operações (tal como instân-
cia versus classe), que abordamos neste capítulo e no Capítulo 1.
3. Resuma os símbolos da UML utilizados para prefixar o nome de uma ca-
racterística (atributo ou operação). Suponha, para este exercício, que os atri-
butos e operações de classe tenham um prefixo com o sinal de cifrão ($).
4. A classe Pessoa tem uma operação altura que, — muito embora seja ape-
nas uma operação obter para seu atributo altura, — tinha de ser exibida
na forma de uma operação explícita porque ela necessitava de um argu-
mento de entrada. (Veja a Figura 3.6.) Por que não “parametrizar” o atri-
buto altura, como altura (data: Data): Comprimento, e dessa forma remover
a necessidade de mostrar suas operações obter e especificar explicitamente?
5. Pesquise a notação da UML para expressar uma classe cujos objetos são
imutáveis (conceito que abordei nos exercícios do Capítulo 1).
104 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

6. Na página 98, dei o nome Frota para uma classe parametrizada vinculada
Conjunto<Carro>. Esse é o melhor nome para essa classe? (Dica: especule
sobre algumas aplicações que utilizariam Conjunto<Carro>, juntamente
com os atuais e futuros campos de aplicabilidade de operações.)
7. Pelo que você viu até o momento, como você caracterizaria a diferença en-
tre uma propriedade e um estereótipo da UML?
8. Mostre, em notação da UML, uma classe de sua preferência (tal como a
classe Hominóide do Capítulo 1). Inclua os atributos e as operações.

3.12 Respostas
1 Aqui constam algumas características diferenciadoras de classes que con-
sideramos neste capítulo e no Capítulo 1. Em cada par, refiro-me primei-
ramente à classe “comum”, o tipo de classe que será encontrado com mais
freqüência:
(i) Não utilitário versus utilitário. A primeira faz surgir objetos distin-
tos. A última não (podendo ser considerada como o único objeto de
sua “classe”).
(ii) Não literal versus literal. A primeira suporta geração de objetos em
run-time. A última tem todos os seus objetos “gerados” implicitamen-
te pelo compilador, e os “identificadores” de objetos são seus valores
literais.
(iii) Mutável versus imutável. Os objetos gerados a partir da primeira po-
dem mudar de estado após a geração (conseqüentemente, essa classe
deveria ser, na realidade, chamada de classe com objetos mutáveis).
A última tem objetos cujos estados não mudam após a geração. A
maioria das classes imutáveis são classes literais, e a grande parte
das classes literais são classes imutáveis.
(iv) Concreta versus abstrata. (Não diferida versus diferida.) A primeira
é uma classe sem operações abstratas. Nenhum objeto pode ser ge-
rado a partir do último tipo de classe, normalmente porque ele tem
uma ou mais operações abstratas. (Entretanto, podem ser gerados
objetos desde uma subclasse concreta de uma classe abstrata.)
(v) Não parametrizada versus parametrizada. (Não genérica versus ge-
nérica; não padrão versus padrão.) A primeira é uma classe simples
que não necessita de quaisquer nomes vinculados à mesma. A últi-
ma é uma classe que vincula nomes de classes como argumentos
quando da geração de objetos.
2. Aqui constam algumas características diferenciadoras de operações que
consideramos neste capítulo e no Capítulo 1:
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 105

(i) Instância versus classe. A primeira operação é definida em uma ins-


tância individual (objeto). A última é definida em uma classe e não
acessa diretamente variáveis de objetos individuais.
(ii) Consulta versus não consulta. (Acesso versus modificador.) A execu-
ção de uma operação de consulta não muda o estado do sistema. A
execução de uma operação de não consulta normalmente muda. A
maioria das operações de consulta são operações obter, com valor de
atributo de acesso. A maioria das outras operações são operações de
não consulta, inclusive operações especificar, com valores de atributo
de mudança.
(iii) Pública versus protegida versus privada. Uma operação pública é vi-
sível do lado externo da classe ou do objeto no qual ela está definida.
Uma operação protegida é visível somente para os objetos da mesma
classe (ou para os de suas subclasses). Uma operação privada é visí-
vel somente para o objeto no qual ela está definida.
(iv) Concreta versus abstrata. (Não diferida versus diferida.) A primeira
é o tipo normal de operação, com uma implementação que pode ser
executada (método composto de um corpo de código). Na última ope-
ração, falta uma implementação real e ela deve ser redefinida em
uma subclasse, herdada por uma implementação executável concreta.
(v) De procedimento versus de atribuição de nome funcional. (Eu admito
que essa distinção ocorre mais em nível de programação que em ní-
vel de desenho.) A primeira retorna seus valores via argumentos, em
vez de via nome de operação. A última espécie de operação retorna
seus valores via nome de operação, e é conveniente para operações
obter.
3. A seguinte tabela resume o uso de prefixos da UML em nomes de carac-
terísticas:

Grupo Símbolo Significado


A / atributo capaz de ser lido e escrito (ambos “acessíveis” e
“determináveis”)
atributo de apenas leitura (read-only) (só “acessível”)
B $ atributo ou operação de instância
atributo ou operação de classe
C atributo ou operação pública (ou ainda a ser decidida)
+ atributo ou operação pública
- atributo ou operação privada
# atributo ou operação protegida
106 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Para os atributos, escolha um do Grupo A, um do Grupo B e um do Grupo


C. Para as operações, escolha uma do Grupo B e uma do Grupo C. Na UML
ortodoxa, o Grupo B é efetivamente representado por, respectivamente, sem
sublinhado ou sublinhado.
4. Eu não mostro atributos parametrizados neste livro porque eles não são
suportados na UML. Entretanto, confesso que utilizo atributos parame-
trizados informalmente, em quadros brancos, especialmente quando es-
tou conseguindo chegar a certos atributos, mas não estou muito pronto
para descrever operações.
5. A UML, de forma bem aceitável, indica a imutabilidade com a proprieda-
de {imutável}. Se você colocar essa propriedade no compartimento supe-
rior (nome) de uma classe, em decorrência os objetos dessa classe serão
completamente imutáveis (isto é, todos os seus atributos serão imutáveis
após a geração). Se você colocar essa propriedade ao lado de um único
atributo no compartimento médio de uma classe, então apenas esse atri-
buto será imutável. (Como se pode ver, “imutável” simplesmente quer di-
zer “constante” em OO.)
Lembre-se de que o conceito de imutabilidade é diferente da idéia de
ser “não diretamente acessível”. Por exemplo, o atributo posição de um
hominóide não é diretamente acessível — e, assim, é marcado com um si-
nal de corte inclinado para a direita (/) — se bem que ele não é imutável:
seu valor muda sempre que o hominóide avança.
6. Uma frota pode ser um monte de carros, mas também pode ser um monte
de barcos ou de aviões. Hoje, uma corporação pode possuir apenas uma
frota de carros, mas, no futuro, seus “mimados” executivos talvez estejam
movendo-se rapidamente de um lado a outro do país em jatos da compa-
nhia, e até mesmo remando em grandes lagos utilizando-se de canoas da
empresa. Nesse caso, necessitaríamos, por exemplo, de FrotaDeAeronaves
e FrotaDeEmbarcações. Todavia, o nome Frota seria então menos efetivo,
especialmente se precisássemos de Frota para ser uma superclasse de Fro-
taDeAeronaves, FrotaDeEmbarcações e FrotaDeCarros. Assim, a fim de evi-
tar muitas mudanças de nomes mais tarde, deveríamos nomear
Conjunto<Carro> como FrotaDeCarros e não somente Frota — desde o iní-
cio.
Isto condensa um par de perguntas vexatórias que surgem quando
você desenvolve sistemas orientados a objeto tendo a reutilização em
mente: Onde esta classe precisará ser reutilizada? Como será o campo de
ação do negócio no futuro?
Cap. 3 EXPRESSÃO BÁSICA PARA CLASSES, ATRIBUTOS E OPERAÇÕES 107

7. Uma propriedade da UML representa uma característica particular de


um elemento padrão da UML. Por exemplo, a propriedade {abstrata} é
acrescentada ao componente de topo de um símbolo de classe abstrata
para representar uma espécie particular de classe. Um estereótipo da
UML, por outro lado, representa uma nova construção de modelagem.
Por exemplo, o estereótipo «utilitário» é adicionado ao compartimento su-
perior do ícone de classe para representar a construção de utilitário da
UML, a qual é distinta da classe. (A maioria das outras notações atribui
um símbolo peculiar a cada construção, o que provoca um excesso preju-
dicial de mistura de símbolos.)
As propriedades e estereótipos da UML são muito úteis. Infelizmente, en-
tretanto, alguns locais de trabalho parecem considerar tão sutil a distin-
ção entre propriedade e estereótipo que não respeitam essa distinção, e
aplicam os dois termos de maneira intercambiável.
8. A Figura 3.16 mostra alguns atributos e operações que podem ser defini-
das em Hominóide.

Figura 3.16 UML para a classe Hominóide.

Há alguns pontos a observar neste exemplo:


(i) Os atributos apontandoParaParede e posição não deveriam ter opera-
ções de conjunto correspondentes; portanto, pedem a notação do si-
nal de corte inclinado para a direita (/).
(ii) Muito embora a maioria das operações tenha argumentos de entrada
ou saída de dados, as operações virarÀEsquerda e virarÀDireita não os
possuem. Isso é legal, mas muitas vezes é uma sugestão ao desenhis-
ta de que uma operação poderia ser generalizada. Por exemplo, po-
deríamos substituir as duas operações por uma operação única, mais
geral, tal como virar (ângulodeVirada). Retornarei a esse tema na se-
ção 14.2.
DIAGRAMAS DE CLASSE
4.Diagramas de Classe
D iagramas de classe

FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

E ste capítulo introduz a UML para diagramas de classe, os quais propi-


ciam aos modeladores três importantes construções orientadas a objeto: a
estrutura de herança, a estrutura de associação e a associação todo/parte.
A seção 4.1 apresenta a estrutura de herança (ou superclasse/subclasse).
Conforme vimos no Capítulo 1, a herança é uma estrutura vital para a cons-
trução de classes baseadas em outras classes. Ela também é a estrutura mais
importante a partir da qual pode-se construir uma biblioteca de classes.
A seção 4.2 apresenta a estrutura de associação. Uma associação entre
classes se dá quando existe algum relacionamento conceitual (geralmente en-
volvendo o negócio que está sendo modelado) vinculando suas instâncias.1 A
UML descreve as associações com — aqui, sem surpresas — a construção de
associação.
A seção 4.3 apresenta as associações todo/parte, pelas quais, por exemplo,
uma cadeira compreende um espaldar, um assento e quatro pernas. Essa es-
trutura é tão comum e útil na orientação a objeto como no cotidiano. Existem
duas formas de associações todo/parte na UML: composição (expressa pelo
adorno de composição no símbolo de associação) e agregação (expressa pelo
adorno de agregação nesse mesmo símbolo).

4.1 A Construção da Generalização


Na UML, a construção da generalização (exibida como uma seta com uma
ponta “aberta”) permite ao desenho indicar quer a herança simples, quer a he-
rança múltipla.

1. De fato, o termo associação da UML é chamado de relacionamento em muitas outras notações.

108
Cap. 4 DIAGRAMAS DE CLASSE 109

4.1.1 Herança simples


A Figura 4.1 mostra uma hierarquia de herança com uma herança simples
(cada subclasse tem uma superclasse). Observe que o sentido da seta vai da
subclasse (a classe de herança mais específica) à superclasse (a classe de he-
rança mais geral). Isso pode parecer estranho: No cotidiano, a herança não
“viaja” no sentido descendente? Ou seja, ações da Ferrovia Transiberiana e
um vaso grande etrusco não podem ser herdados de nosso bisavô?

Figura 4.1 Hierarquia de herança simples.

Entretanto, a seta representativa da herança aponta para cima na hie-


rarquia devido ao fato de que o sentido de referência da classe é ascendente.
Em outras palavras, uma subclasse Carro pode se referir a sua superclasse Veí-
culoAMotor, mas VeículoAMotor normalmente não faria qualquer alusão a Car-
ro. A seta veio a ter outra conotação intuitiva valiosa: ela também representa
o caminho ao longo do qual um objeto de uma dada classe procurará pela ope-
ração que ele necessita herdar e executar. Primeiramente, ele procura em sua
própria classe, depois na classe de seus pais, em seguida na classe de seus
avós, e assim por diante.
A Figura 4.2 mostra um estilo alternativo para retratar herança.2 Em
vez de setas separadas para a superclasse, há apenas uma seta, que se bifurca
nas subclasses. Muito embora a Figura 4.2 seja semanticamente equivalente
à Figura 4.1, prefiro este último estilo de representação porque, como veremos
adiante, ele capacita-nos a especificar propriedades para as subclasses (do tipo
de restrições provenientes de divisões) no diagrama.

2. A UML se refere a esse diagrama semelhante a uma árvore denominando esse estilo de alvo
compartilhado.
110 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 4.2 Hierarquia de herança simples — notação do “alvo compartilhado”.

A Figura 4.3 mostra um outro estilo para retratar superclasses e subclas-


ses, dessa vez sem os compartimentos destinados aos atributos e operações.
Eu normalmente utilizo esse estilo reduzido quando não há necessidade de se
exibir quaisquer detalhes além dos próprios nomes de classe.

Figura 4.3 Hierarquia de herança simples — notação reduzida normal.

Incidentalmente, se você deseja documentar uma hierarquia de classe ba-


seada em herança simples, destituída dos nomes das características,3 não é
necessário um diagrama: pode documentar sua hierarquia com um texto en-
dentado (recuado) simples. Essa é exatamente a forma como muitos navega-
dores (browsers) de biblioteca, destinados a vendedores, retratam a herança.4

3. Como lembrete, uma característica constitui um termo para um atributo ou operação.


4. A herança múltipla também pode ser expressa textualmente na forma de uma matriz de he-
rança direta, uma matriz booleana que mostra qual classe é uma superclasse direta de quem.
A matriz de herança total é derivada dessa primeira matriz, calculando-se o assim denomi-
nado fechamento transitivo. Entretanto, não tente fazer isso em casa; isso definitivamente
constitui um serviço para uma ferramenta de modelagem automatizada.
Cap. 4 DIAGRAMAS DE CLASSE 111

Por exemplo, se Carro e Caminhão forem subclasses de VeículoAMotor, po-


deríamos escrever:
VeículoAMotor
Carro
Caminhão
Entretanto, essa representação de herança do tipo listagem endentada
não consegue mostrar simultaneamente atributos e operações muito facilmen-
te, enquanto felizmente, a notação de generalização da UML consegue mos-
trar essas características acrescidas nos símbolos de classe.

4.1.2 Herança múltipla


A Figura 4.4 mostra a UML para a herança múltipla, por meio da qual uma
classe é derivada diretamente de mais de uma superclasse. Na UML, não há
nada significativo na disposição, da esquerda para a direita, — de superclas-
ses diretas de uma classe. Além disso, também não há nada relevante na dis-
posição, da esquerda para a direita, de subclasses de uma classe.

Figura 4.4 Hierarquia de herança múltipla.

4.1.3 Divisão em subclasses


A Figura 4.5 mostra novamente a classe VeículoAMotor e suas subclasses. Ob-
serve que escrevi a seta de generalização neste diagrama com as propriedades
disjunção (disjoint) e incompleto (incomplete). Deixe-me explicar o que signifi-
cam esses termos.
112 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 4.5 Hierarquia tendo as propriedades disjunção e incompleto.

O termo particionamento de disjunção (disjoint partitioning) (em contra-


posição ao particionamento de sobreposição [overlapping partitioning]) aplica-
se a dois ou mais grupos de coisas que são habilmente divididos. Em outras
palavras, nenhum item individual pode pertencer a mais de um grupo ao mes-
mo tempo. Por exemplo, cachorros, porcos e cavalos são — à época em que es-
crevia este livro! — grupos disjuntos. Como ilustração dos termos disjunção e
sobreposição, veja a Figura 4.6.

Figura 4.6 Ilustração de grupos disjuntos e sobrepostos.

O termo particionamento incompleto (incomplete partitioning) (em con-


traposição ao particionamento completo [complete partitioning]) aplica-se aos
subgrupos de um grupo. Ele significa que nem todos os subgrupos possíveis
do grupo estão incluídos no modelo. Em outras palavras, o grupo pode ter al-
guns membros que não pertençam a quaisquer dos subgrupos modelados. Isso
não implica que o modelador tenha a intenção de acrescentar mais subgrupos
ao modelo; os subgrupos correntemente modelados podem constituir tudo o
que é necessário para o alcance da aplicação.
Como ilustração dos termos incompleto e completo, veja a Figura 4.7, na
qual o grupo como um todo é representado pelo contorno pontilhado, e os sub-
grupos são representados pelos contornos com linha cheia.
Cap. 4 DIAGRAMAS DE CLASSE 113

Figura 4.7 Ilustração de subgrupos completos e incompletos de um grupo.

Para exemplificar o particionamento incompleto em palavras, vamos di-


zer que o grupo é pessoas e que os subgrupos são trapezistas de circo, pára-
quedistas acrobatas, vigaristas ousados, vendedores ambulantes de ungüento
de cobra e metodologistas. Claramente, os subgrupos são incompletos (em re-
lação ao grupo de todas as pessoas), devido ao fato de que eles não incluem,
por exemplo, os funcionários de suporte a usuários de computadores ou os
adestradores de doninhas. (Eu não tenho muita certeza se eles são ou não so-
brepostos.)
Se você estiver utilizando uma ferramenta de modelagem, note que a di-
visão incompleta não é determinada pela porção do diagrama que está visível
em sua tela de exibição. Muitas ferramentas de modelagem utilizam elipses
(...) para indicar que algumas subclasses não estão exibidas na janela aberta.
As subclasses mostradas podem ser incompletas, mas o modelo como um todo
ainda pode apresentar uma divisão completa.
O termo particionamento dinâmico (dynamic partitioning) (em contrapo-
sição ao particionamento estático [static partitioning]) aplica-se a uma associa-
ção de coisas em mais que um subgrupo durante o período de tempo. Com o
particionamento dinâmico, uma coisa pode iniciar a vida como membro de um
subgrupo, porém, mais adiante, pode tornar-se membro de um subgrupo dife-
rente. Por exemplo, conforme ilustrado pela Figura 4.8, um funcionário talvez
não seja um gerente neste ano, mas pode ser um gerente no próximo ano —
ou vice-versa!
Embora os conceitos de particionamento de disjunção/sobreposição, parti-
cionamento de completo/incompleto e particionamento dinâmico/estático são
preciosos no início de um projeto (quando a equipe de trabalho estiver anali-
sando os requisitos do negócio), eles também permanecem muito valiosos na
fase do desenho.
114 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 4.8 Hierarquia de herança dinâmica.

Se souber que duas classes estão mutuamente sobrepostas, conseqüente-


mente você tem conhecimento de que poderá haver uma subclasse de utilidade
herdeira de ambas as classes. Por exemplo, na Figura 4.9, as subclasses de so-
breposição da classe Animal, AnimalHerbívoro e AnimalCarnívoro poderiam ter
uma subclasse comum, derivada de herança múltipla, AnimalOnívoro.

Figura 4.9 Hierarquia de herança sobreposta e completa.

Retornarei às implicações em desenho do particionamento dinâmico no


decorrer do livro, especialmente no exercício final do Capítulo 14.

4.1.4 Discriminadores de particionamento


A Figura 4.10 mostra uma hierarquia de heranças com dois níveis. No nível
superior, a classe Veículo tem duas subclasses: VeículoPropulsionadoExterna-
mente e VeículoPropulsionadoInternamente (Marquei esse particionamento
como de disjunção porque não estou considerando meu antigo Rolls Canardly,
o qual, muito embora, nominalmente autopropulsionado, passou uma grande
parte de sua existência sendo empurrado). O particionamento é completo pelo
Cap. 4 DIAGRAMAS DE CLASSE 115

fato de que assumo que a fonte propulsora de um veículo deve ser ou externa
ou interna, não havendo qualquer outra opção.

Figura 4.10 Hierarquia de herança com discriminadores.

Repare, na anotação da Figura 4.10, o discriminador LocalizaçãoDaFonte-


Propulsora. Informalmente, isso representa: “O que diferencia os veículos que
pertencem a VeículoExternamentePropulsionado dos veículos que pertencem a
VeículoInternamentePropulsionado é a localização de suas fontes propulsoras”.
Em termos de desenho orientado a objeto, os objetos da classe Veículo prova-
velmente terão um atributo nomeado de LocalizaçãodaFontePropulsora. Os ob-
jetos da classe VeículoExternamentePropulsionado terão aquele atributo fixado
como o valor constante externo, enquanto os objetos da classe VeículoInterna-
mentePropulsionado terão o mesmo atributo fixado imutavelmente como o va-
lor constante interno.
Como mencionei anteriormente, uma superclasse completamente dividi-
da em compartimentos, em subclasses, pode tornar-se uma classe abstrata
(uma classe destituída de objetos gerados a partir dela). Isso porque, em uma
partição completa, não existem objetos dispersos (“stray”). Todos os objetos
que puderem ser gerados a partir da superclasse têm uma residência mais
precisa em uma das subclasses. Por exemplo, na Figura 4.10, a classe Veículo
se tornaria uma classe abstrata, já que todo objeto veículo real criado será
uma instância, quer de VeículoExternamentePropulsionado, ou quer de Veícu-
loInternamentePropulsionado.
116 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

No menor nível da hierarquia de herança, marquei o particionamento da


subclasse de VeículoExternamentePropulsionado (segundo o discriminador tipo-
DeVeículo) como de disjunção e incompleto. A disjunção é óbvia; o particiona-
mento é certamente do tipo incompleto devido ao fato de que está faltando a
subclasse Reboque (entre muitas outras). Assim, aposto que VeículoExterna-
mentePropulsionado não se tornaria uma classe abstrata — e nós, provavel-
mente, precisaríamos de gerações tais como:

var meuReboque:= VeículoExternamentePropulsionado.Novo

Marquei o particionamento da subclasse de VeículoInternamentePropulsio-


nado (segundo o discriminador meioDoVeículo) como de sobreposição e incom-
pleto. Ele é de sobreposição pelo fato de que um veículo anfíbio pode navegar
tanto em terra como na água. Na realidade, quando empurrei o meu Rolls Ca-
nardly para dentro do rio, em uma ação desonesta para obter o prêmio de se-
guro, ele flutuou para longe como um “boato falso” e retornou para mim dois
dias depois. (Pedido negado!) Essas classes sobrepostas irão provavelmente ter
uma subclasse comum.
O particionamento de VeículoInternamentePropulsionado é do tipo incom-
pleto porque estão faltando os veículos aéreos e espaciais (entre outros). A
classe de VeículoInternamentePropulsionado presumivelmente será designada
como uma classe concreta, permitindo dessa forma que sejam gerados objetos
aéreos e espaciais.

4.2 A Construção de Associação


Uma associação na UML representa uma população variada de vínculos
(links) de relacionamento entre instâncias de classe. O que isso significa me-
lhor explicado por meio de um exemplo: vamos dizer que temos duas classes,
UsuáriosDaBiblioteca e LivrosDaBiblioteca. Uma associação entre elas poderá ser
Empréstimos, um conjunto de vínculos de relacionamento que estabelecem
qual o usuário que, no momento, está tomando por empréstimo determinado
livro. Hoje, a associação Empréstimos talvez contenha estes sete vínculos:
Fred tomou emprestado Construção de Iglus para Iniciantes.
Sally tomou emprestado O Frêmito da Contabilidade de
Custos.
Mary tomou emprestado Mecânica Quântica para
Surrealistas.
Cap. 4 DIAGRAMAS DE CLASSE 117

Stan tomou emprestado Três Gerações de Wellingtons no


Campo.
Stan tomou emprestado Pensamentos Confusos.
Genghis tomou emprestado Viajando de Bicicleta pelos Andes.
Brunhilde tomou emprestado Redução de Tamanho de Empresas
em Ação: Branca de Neve e os Três
Anões.

Amanhã, a associação Empréstimos poderá conter estes oito vínculos:


Fred tomou emprestado Triste Consolo: a Estrada de Volta
da Hipotermia.
Mary tomou emprestado Mecânica Quântica para
Surrealistas.
Mary tomou emprestado Cálculo de Tensores Relativistas em
Quatro-Dimensões para Programas
de Simulação.
Stan tomou emprestado Traga-me meu Casaco Desejado.
Genghis tomou emprestado Biggles está Voando Baixo.
Genghis tomou emprestado Biggles está Arrancando.
Wally tomou emprestado Biggles está Aguentando Firme.
Brunhilde tomou emprestado Confissões de um Arquiteto de
Software Adolescente.

No próximo mês — a biblioteca estará fechada para reformas — talvez


não existam instâncias de Empréstimos.
Como mostrado por esse exemplo, cada vínculo em uma associação (biná-
ria) atrela em conjunto uma instância da primeira classe com uma instância
da segunda classe, refletindo determinado relacionamento de negócio entre es-
sas instâncias. O relacionamento de negócio nesse exemplo é o empréstimo de
livros.
Repare que Empréstimos representa uma coleção de vínculos, com o nú-
mero real destes se alterando no decorrer do tempo: primeiro sete, depois oito
e em seguida zero. A esse respeito, uma associação é como uma classe cujos
vínculos são suas instâncias.
118 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

4.2.1 Notação básica da UML para associações


A Figura 4.11 contém três associações, cada uma delas mostrada como uma
linha entre duas classes.5 O nome da associação aparece sobre essa linha. Por
exemplo, uma vez que as companhias empregam pessoas, existe uma associa-
ção nomeada Emprego entre as duas classes Companhia e Pessoa.

Figura. 4.11 Três associações entre pares de classes.

O papel de cada classe pode aparecer ao lado da mesma, no final da linha.


Na Figura 4.11, a classe Pessoa assume o papel de empregado na associação
Emprego, enquanto a classe Companhia assume o papel de empregador nessa
mesma associação. A multiplicidade das operações também surge nos finais de
cada linha6. Por exemplo, uma dada pessoa é um empregado em 0 ou 1 com-
panhia; uma dada companhia é um empregador de 0 a muitas pessoas — isto
é, de qualquer número de pessoas.
Modelar associações constitui a espinha dorsal de uma análise técnica po-
derosa denominada modelagem de informações, e eu não deveria tentar aqui
fazer justiça a isso. Todavia, realçarei alguns pontos, no interesse da clareza:

1. Você pode utilizar diagramas de associação para obter informações a par-


tir de diversas perspectivas de modelagem7. Por exemplo, sob uma pers-
pectiva essencial, você pode simplesmente mostrar nomes de classes. Sob

5. Em certos casos, a mesma classe aparece em ambas as extremidades da associação.


6. Algumas pessoas utilizam o termo cardinalidade para multiplicidade.
7. Eu discuti as perspectivas de modelagem no Capítulo 2.
Cap. 4 DIAGRAMAS DE CLASSE 119

uma perspectiva de especificação, pode acrescentar as operações e atribu-


tos que suportam as associações. Sob uma perspectiva de implementação,
você deve considerar de que forma seu código orientado a objeto suporta
uma navegação eficiente pelas associações. (Veja a seção 4.2.4 para mais
informações sobre navegação.)
2. Uma associação, ou relacionamento, é geralmente nomeada por uma for-
ma verbal na modelagem de informações convencional (se realmente exis-
tir uma coisa dessas — uma vez que há 94 dialetos de UML, cada um
deles vigiado por uma tribo feroz). Por exemplo, muitos modeladores de
informações nomeariam a associação Emprego como está empregado em
(lendo de Pessoa à Companhia), e, fazendo a leitura no outro sentido, a no-
meariam emprega. Os modeladores no mundo orientado a objeto, entre-
tanto, preferem nomear uma associação com um substantivo singular. A
razão: uma associação é efetivamente uma classe, que, de preferência, é
nomeada com um substantivo. (retornaremos a esse ponto na seção 4.2.2.)
3. Uma parte da multiplicidade mostrada na Figura 4.11 pode ser questio-
nável. Por exemplo, como uma pessoa pode ser empregada em nenhuma
companhia? Por que estaríamos interessados em uma pessoa como essa?
Sem conhecermos a finalidade do projeto que concebeu esse fragmento de
modelo, não posso dizer com toda a certeza. Se estivermos interessados
em uma esfera de negócios que inclui só pessoas empregadas, deveremos
mudar a multiplicidade para 1..1. Caso contrário — isto é, se tivermos
uma mistura de pessoas empregadas e desempregadas em nosso domínio
de interesse —, então, deveremos deixar a multiplicidade do jeito como
ela se encontra. De maneira similar, como pode uma companhia ter ne-
nhuma pessoa empregada? Provavelmente ela não pode, mas a questão é
se nosso software deverá (ou não impor um limite inferior com respeito à
contagem de funcionários.
4. A associação à esquerda, na Figura 4.11, mostra que estamos interessa-
dos em qual pessoa tem residência em determinado município. Nós talvez
iríamos supor que a multiplicidade se refere à principal residência de um
indivíduo, porque cada pessoa parece ter residência em exatamente um
município. Mas a adivinhação não é boa o suficiente, especialmente se a
multiplicidade puder estar incorreta. Os analistas devem definir cuidado-
samente os termos comerciais tal como residência. Se eles se omitirem em
fazê-lo, então, nós, na qualidade de desenhistas, temos o imperativo mo-
ral de gritar, espernear e rasgar nossas camisas em protesto.
120 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

5. A associação à direita, mostra que estamos interessados em qual compa-


nhia tem localização em determinado município. A multiplicidade da as-
sociação Localização implica que estamos interessados em todos os
municípios em que determinada companhia esteja localizada, não exata-
mente a sua principal localização ou sede. Talvez nós precisemos dessa
associação para fins de taxação de impostos — mas isso não é bom o su-
ficiente: um analista íntegro e reto deverá definir e documentar cuidado-
samente o significado e propósito dessa associação.

E, agora, vamos apresentar alguns pontos especificamente sobre a nota-


ção da UML para associações:

1. A UML não insiste em um nome para uma associação. Entretanto, você


deveria tentar nomear todas as suas associações (a menos que elas sejam
associações de agregação e composição, que abordarei na próxima seção).
Trata-se de um bom exercício, e você se sentirá melhor posteriormente
(endorfinas, suponho).
2. A UML, da mesma forma, não requer nomes de papel. Legalmente, você
pode ter uma associação por meio da UML sem nomes de papel, outra
com um e ainda uma outra com dois nomes de papel. Isso está perfeito
para mim. Tente encontrar nomes de papel significativos sempre que pos-
sível, mas não crie dificuldades quanto à credulidade do usuário ao surgir
com nomes artificiais, especialmente quando a classe, no final da linha e
em seu próprio termo, indicar o papel dela.8
3. As ferramentas de modelagem na UML freqüentemente abreviam a mul-
tiplicidade desta forma: 0..* (qualquer número) torna-se simplesmente
um asterisco (*). De maneira similar, 1..1 (exatamente um) é mostrada
como 1.

4.2.2 Associações representadas como classes


A Figura 4.12 mostra uma associação de posse entre pessoas e cachorros, re-
fletindo o fato de que uma pessoa pode possuir qualquer número de cachorros,
e um cachorro pode ter como dono unicamente uma pessoa (neste modelo, não
há extravio e nem custódia conjunta de cachorros). Como usual, denominei a
associação de: a PosseDeCachorros.

8. Alguns modeladores hábeis e experientes têm um ponto de vista diferente, e modelam asso-
ciações utilizando somente nomes de papel e sem um nome de associação. Infelizmente, em
mãos menos hábeis, essa prática muitas vezes conduz a associações vagas ou ambíguas.
Cap. 4 DIAGRAMAS DE CLASSE 121

Figura 4.12 Associação básica entre duas classes.

A Figura 4.13 mostra uma associação semanticamente idêntica àquela


constante na Figura 4.12. Entretanto, o nome da associação (PosseDeCachor-
ros) moveu-se para o topo para tornar-se o nome de uma classe, o qual fica vin-
culado à linha de associação por meio de uma linha pontilhada.

Figura 4.13 PosseDeCachorros promovida à condição de classe.

Promover a associação dando a ela condição de classe permite-nos espe-


cificar atributos e operações à mesma; os atributos e operações que pertencem
à posse do cachorro em vez de a um cachorro ou uma pessoa.9 Por exemplo,
poderíamos ter /dataDeAquisição: Data e /dataFinal: Data como atributos. Tam-
bém poderíamos ter transferirPosse (dataFinal: Data) como uma operação. Nós
ainda poderíamos modelar outra associação (tal como Licenciamento entre Pos-
seDeCachorros e Município), refletindo nosso interesse em saber qual município
está licenciado quanto à instância da posse de cachorros.

9. Lembre-se de que, formalmente falando, a associação tem sido uma classe o tempo todo (cujas
instâncias são vínculos). Assim, como muitas em minha própria carreira, essa promoção é
apenas simbólica.
122 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

4.2.3 Associações de ordem mais alta


Até o momento, analisamos a associação binária de duas classes, representada
pela UML como uma linha. Mas como é exibida uma associação ternária ou
de três vias? Conforme exibido pela Figura 4.14, a resposta é: por um diaman-
te, que embeleza a junção das três linhas de associação. (A UML pegou em-
prestado o símbolo do diamante do dialeto Chen, usado em modelagem de
informações.)

Figura 4.14 Associação ternária.

A associação da Figura 4.14 é parte de um modelo de atividade de com-


pras destinado à aquisição de itens a partir de vendedores. Nesse tipo de ne-
gócio, o preço unitário depende de três fatores: o tipo de item (o produto); a
companhia que o está vendendo (o vendedor); e a quantidade de itens compra-
dos (o nível de retenção de preços). Nós, portanto, necessitamos de uma asso-
ciação ternária que envolva o TipoDeItem, a Companhia e a Quantidade
Comprada a fim de ter uma residência apropriada para o atributo preçoUnitá-
rio, o qual é determinado pelas instâncias daquelas três classes.
Permita-me levantar um ou dois pontos pragmáticos sobre relacionamen-
tos ternários:
Cap. 4 DIAGRAMAS DE CLASSE 123

1. Você pode apostar que a multiplicidade em todas essas três classes será
0..*. (Do contrário, a associação ternária conseqüentemente poderia ser
geralmente resolvida em duas ou três associações binárias.)
2. Muito provavelmente você necessitará promover a associação para uma
classe exclusiva dela — aqui, é CatálogoParaCompraDeItens —, devido ao
fato de que a associação terá, no mínimo, um atributo ou uma operação.
(Do contrário, a associação ternária conseqüentemente poderia ser resol-
vida de novo em duas ou três associações binárias.)
Associações ternárias proveitosas são muito mais raras de se encontrar
do que as associações binárias. Associações quaternárias (ou de quatro vias)
são ainda mais raras. Se alguma aparecer em seu próximo projeto, você pode-
rá mostrá-la exatamente igual a uma associação ternária (mas com quatro li-
nhas sólidas tocando o diamante).

4.2.4 Navegabilidade de associações


A navegabilidade é um conceito da UML que reservo inteiramente para os mo-
delos de implementação OO. Em outras palavras, tenho a propensão de acres-
centar os símbolos de navegabilidade em meus diagramas quando do final do
desenho (isso quando realmente os acrescento). Dito isso, vamos examinar o
que é navegabilidade e como ela é exibida pela UML.
Na Figura 4.15, a primeira navegabilidade (a seta de Pessoa a Cachorro)
indica que a implementação suportará uma cômoda e rápida passagem desde
um objeto Pessoa até os objetos Cachorro possuídos pelo mesmo.
Você pode construir essa navegabilidade em cada objeto Pessoa, atribuin-
do a um conjunto de cachorros a seguinte declaração de variáveis dentro da
classe Pessoa:10

cachorrosPossuídosPor: Conjunto <Cachorro>;

A segunda navegabilidade na Fig. 4.15 (a seta indo de Cachorro a Pessoa)


indica que a implementação suportará uma cômoda e rápida passagem desde
um objeto Cachorro até o objeto Pessoa que o possui. Ou seja, você poderá en-
contrar rapidamente o dono de um dado cão. Novamente, não há garantia do
oposto. Isto é, se você conhecer um dono, esse mecanismo não oferecerá qual-
quer facilidade para que se encontrem os cachorros do mesmo.

10. Eu retorno às opções de implementação no final da seção 4.3.1, e discuto-as mais adiante no
exercício 4 do Capítulo 14.
124 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 4.15 Três possíveis navegabilidades para uma


associação entre duas classes.

Você pode construir essa navegabilidade em Cachorro com a seguinte de-


claração simples, beneficiando-se da multiplicidade de 1, de Cachorro a Pessoa:

dono: Pessoa;

A terceira navegabilidade na Figura 4.5 combina as duas primeiras. Com


ela, você poderá percorrer a associação rapidamente em ambas as direções. As
classes Pessoa e Cachorro irão dispor de referências mútuas em suas declara-
ções de variáveis.

4.3 Associações Todo/Parte


A UML tem uma notação especial para as associações todo/parte de composi-
ção e agregação. Nos últimos anos, o debate sobre os significados desse infame
par deixou muitas pessoas sem compreenderem a floresta mesmo vendo as ár-
vores. O problema é que existem, na realidade, sete ou oito variedades de
construções todo/parte no mundo real, e qualquer tentativa de reduzi-las à
composição e agregação pode resultar em confusão e provocação.11
Todavia, colocando um pouco o cérebro para funcionar, você pode admi-
nistrar bem essa redução na maioria das situações. Mas, se a sua equipe de

11. Você pode encontrar um resumo esclarecedor das várias construções todo/parte em Martin e
Odell, 1995. Eu também menciono algumas delas nos exercícios no final deste capítulo.
Cap. 4 DIAGRAMAS DE CLASSE 125

projetistas achar isso muito trabalhoso, recomendo utilizar só uma construção


(digamos, agregação). Como veremos nesta seção, você pode usar a notação de
multiplicidade padrão da UML para expressar quaisquer sutilezas posteriores
que envolvam conceitos de todo/parte.

4.3.1 Composição
Composição é uma estrutura comum em sistemas de software, orientados ou
não a objeto, devido ao fato de que muitos objetos compostos aparecem no
dia-a-dia. Por exemplo, um cachorro é uma composição contendo uma cabeça,
um corpo, um rabo e quatro patas (uma em cada extremo). Outras composi-
ções são mais conceituais ou específicas de software: por exemplo, uma men-
sagem de e-mail é uma composição contendo um cabeçalho e alguns
parágrafos de texto. Por sua vez, o cabeçalho é uma composição do nome do
remetente, do nome do destinatário, do título da mensagem e de algum outro
e-stuff (item eletrônico).
Agora, antes de prosseguirmos, vamos ver um pouco de terminologia.
Essa associação todo/parte é chamada de composição; o “todo” é denominado
[objeto] composto; a “parte” é denominada [objeto] componente.12
As três características mais importantes da composição são os que se-
guem:
1. O objeto composto não existe sem os seus componentes. Por exemplo, re-
mova as cerdas, o cabo e a pecinha de borracha de uma escova de dentes
e você não mais terá uma escova de dentes. Na verdade, a simples remo-
ção das cerdas de uma escova de dentes faz com que ela dificilmente seja
qualificada como tal. Dessa forma, o tempo de vida de um objeto composto
não pode ultrapassar o tempo de vida de seus componentes.
2. A qualquer hora, cada dado objeto componente pode ser parte de somente
um objeto composto.
3. A composição é tipicamente heterômera (cujas partes não são semelhan-
tes). Os componentes provavelmente são de tipos misturados: algumas ro-
das, alguns eixos, alguns pedaços de madeira... e aí está a sua carroça.

Agora é hora de verificarmos uma composição de modelagem na UML. O


objeto composto na Figura 4.16 representa um planador simplificado. Ele tem
quatro componentes: fuselagem, cauda, asa esquerda e asa direita.

12. Aqui, utilizo o termo componente em seu tradicional sentido de “peça”; eu não atribuo ao mes-
mo o significado de “construção de software pré-fabricado”, que é o significado do componente
do Capítulo 15.
126 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 4.16 Objeto composto e seus componentes.

Vamos dissecar essa figura para ver como a UML funciona:

1. Uma associação entre o objeto composto e cada um dos objetos componen-


tes aparece no diagrama sob a forma de uma linha de associação, com um
pequeno diamante preto adornando a extremidade junto ao objeto com-
posto.13
2. A classe do objeto composto, Planador, aparece em uma extremidade da
linha de associação. A classe de cada componente — Fuselagem, Cauda,
Asa e Asa — aparece na outra extremidade. Note que um componente
como Asa somente precisa aparecer uma vez, muito embora essa seja a
classe para dois componentes.
3. O papel do objeto componente na composição aparece na extremidade do
componente na linha de associação. Os papéis asaEsquerda e asaDireita
constituem bons exemplos: cada um deles é um objeto da classe Asa, mas
cada um desempenha um papel diferente em um planador. (Está bem, sei
que asas esquerdas e asas direitas não são exatamente intercambiáveis,
mas eu disse que neste exemplo se tratava de um planador simplifica-
do.)14
4. Você pode (e deve) exibir multiplicidade na extremidade do componente
de cada linha de associação. Se a multiplicidade na extremidade do com-
posto não for mostrada, então assume-se que ela seja exatamente igual a
1. Na Figura 4.16, toda multiplicidade é exatamente igual a 1.

13. Diferentemente dos diagramas de herança de classe que vimos na seção 4.1, os diagramas de
composição normalmente mostram hierarquias no estilo de “alvo separado” ou de “não asse-
melhado à árvore”.
14. As caixinhas cinza, tais como aquelas em torno de asaEsquerda e asaDireita na Figura 4.16,
são apenas para esclarecer a parte gráfica e não apresentam qualquer significado semântico.
Cap. 4 DIAGRAMAS DE CLASSE 127

5. A linha de associação não tem qualquer nome, o que corresponde a uma


norma tanto para a composição como para a agregação. A razão: rara-
mente um nome para uma associação de composição acrescenta algum
significado além do significado todo/parte já indicado pela simbologia.
Verbos triviais, tais como tem, compreende, consiste de, e assim por dian-
te, não acrescentam nada ao modelo.

Uma vez que a composição é uma forma de associação, você pode imple-
mentar navegabilidade da mesma maneira que discutimos na seção 4.2.4. Por
exemplo, a classe Planador poderá ter as seguintes variáveis declaradas dentro
dela:
fuselagem: Fuselagem;
cauda: Cauda;
asaEsquerda: Asa;
asaDireita: Asa;
Quando um objeto — vamos indicá-lo como planador1 — da classe Plana-
dor, é gerado (e inicializado), a variável cauda aponta para um objeto repre-
sentativo da cauda do planador1. De maneira similar, as variáveis fuselagem,
asaEsquerda e asaDireita detêm os identificadores dos outros componentes de
um objeto Planador.
Essa implementação suporta navegabilidade de um objeto composto a
seus objetos componentes. Para mostrar isso na Figura 4.16, você poderá
acrescentar setas de navegabilidade de Planador a, respectivamente, Fusela-
gem, Cauda, Asa e Asa. Você também poderá deixar que os objetos componen-
tes detenham o identificador do objeto composto a fim de implementar a
navegabilidade de um objeto componente ao objeto composto. Sua escolha do
desenho de navegabilidade dependerá dos seguintes fatores:

• quão freqüente e rapidamente a composição precisa ser transposta


para cada direção;
• se os objetos componentes necessitarem ser reutilizados em outra si-
tuação sem o objeto composto (em caso afirmativo, eles não deverão
conter quaisquer referências ao objeto composto).

A composição geralmente anda lado a lado com a propagação de mensa-


gens. Por exemplo, para mover um símbolo de retângulo em uma tela, você po-
deria indicar ao objeto retângulo que ele se mova sozinho. Por sua vez, o
retângulo poderia enviar uma mensagem a cada uma de suas linhas compo-
nentes, para ordenar às mesmas que se movam. De maneira similar, para
128 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

achar o peso de uma cadeira, você poderia enviar uma mensagem a cada com-
ponente da cadeira, pedindo a eles o peso de cada componente.
Nos casos em que precisar de propagações de mensagens desse tipo, você
geralmente suporta tal situação desenhando a navegabilidade em suas asso-
ciações de composição. No último exemplo, você se certificaria de que, para
qualquer objeto Cadeira, você poderia rapidamente obter o identificador do ob-
jeto AssentoDaCadeira, do objeto EspaldarDaCadeira e de cada um dos objetos Pé-
DaCadeira que a compõem. No Capítulo 5 (no diagrama de interação entre
objetos), mostro a notação da UML para mensagens aos componentes de um
objeto composto.

4.3.2 Agregação
Semelhante à composição, a agregação é uma construção familiar por meio da
qual os sistemas de software representam estruturas da vida real. Por exem-
plo, uma cidade é um agregado de casas, uma floresta é um agregado de ár-
vores, e um rebanho é um agregado de ovelhas.15 Em outras palavras, a
agregação é uma associação grupo/membro.
Novamente, vamos ter um pouco de terminologia. A associação é chama-
da agregação; o “todo” é denominado [objeto] agregado; a parte é denominada
[objeto] constituinte.16 As três características mais importantes da agregação
são as seguintes:
1. O objeto agregado pode potencialmente existir sem os seus objetos cons-
tituintes. Por exemplo, mesmo que demita todos os funcionários de um
departamento, você ainda tem um departamento. Entretanto, essa pro-
priedade nem sempre é útil. Eu consigo imaginar uma cidade destruída
e vazia, mas que tal um rebanho vazio? Indagando aos mestres do Zen,
eu gostaria de saber: vocês conseguem vigiar um rebanho sem ovelhas?17
2. A qualquer hora, cada objeto pode ser um constituinte com mais de um
agregado. Novamente, um agregado do mundo real pode ou não aprovei-
tar-se dessa propriedade. Uma pessoa pode pertencer a mais do que um

15. De fato, o rebanho de ovelhas era o agregado original, conforme revelado pela etimologia da
palavra (grex significa “rebanho”).
16. Embora eu tenha a tendência de utilizar o termo simples membro, em vez do pomposo cons-
tituinte, o qual é o termo padrão da UML, evito utilizar membro aqui porque me coloca em
problemas nos locais de trabalho que utilizam C++, em que ele representa a abreviação da
função membro. Se você quiser ser realmente desafiador, tente agregando. Porém, se você qui-
ser uma vida simples, queira aderir a todo e parte tanto para a composição como para a agre-
gação.
17. Uhmmm! Talvez esse seja o motivo pelo qual os pastores levaram tanto tempo para inventar
o conceito de zero.
Cap. 4 DIAGRAMAS DE CLASSE 129

clube no qual freqüentemente ela faça suas refeições, mas pode uma ove-
lha pertencer a mais de um rebanho? Os mestres do Zen mencionados es-
tão novamente silenciosos sobre esse ponto discutível.
3. A agregação tende a ser homeômera (cujas partes são todas semelhan-
tes). Isso significa que os constituintes de um agregado típico pertencerão
à mesma classe. Por exemplo, os constituintes de um parágrafo são todos
sentenças, enquanto os constituintes de uma floresta são todos árvores.
Agora, vamos examinar a notação da UML para agregação. A Figura 4.17
mostra um relatório de gerência que é composto de parágrafos de texto (por
simplicidade). Pode-se fazer as seguintes observações sobre a Figura 4.17:

1. Uma associação entre o agregado e seus constituintes é denotada por um


pequeno diamante aberto na extremidade do agregado da linha de asso-
ciação.
2. As classes do agregado (RelatórioDeGerência) e do constituinte (Parágrafo)
aparecem nas respectivas extremidades da linha de associação. O papel
do objeto constituinte parteDoTexto também aparece na extremidade do
constituinte da linha de associação.

Figura 4.17 Objeto agregado e seus constituintes.

3. Com a agregação, você deverá mostrar a multiplicidade em ambas as ex-


tremidades da linha de associação porque você não pode assumir a mul-
tiplicidade em todos os lugares, da forma como se fez com a associação de
composição. A multiplicidade na extremidade do agregado da Figura 4.17
é 0..*; isso significa que um parágrafo pode pertencer a muitos relatórios
de gerência ao mesmo tempo ou pode pertencer a um relatório ou a ne-
nhum. A multiplicidade na extremidade do constituinte também é 0..*;
130 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

isso significa que um relatório de gerência pode compreender muitos pa-


rágrafos, ou um parágrafo, ou nenhum (relatório vazio).
4. A propriedade {ordenado} na associação revela que os parágrafos estão
em uma seqüência definida. Muito embora você possa também encontrar
essa propriedade em vários componentes em uma composição, ou em ou-
tras associações, eu encontrei-a mais freqüentemente em constituintes de
um agregado. Ocasionalmente, você pode acrescentar outras proprieda-
des a uma associação com multiplicidade de 0..* ou 1..*. Por exemplo,
{Saco} toleraria que (neste exemplo) o mesmo parágrafo aparecesse diver-
sas vezes no mesmo relatório. O padrão é {Conjunto}, que proíbe cópias.
Para enfatizar os detalhes de implementação em seu desenho, você pode
até mesmo especificar {Lista} ou {Árvore}, se bem que se deve evitar o so-
brecarregamento de suas associações com propriedades irrelevantes a
seus leitores.
Da mesma forma que com a composição, você pode implementar a agre-
gação por meio do uso de variáveis. Pela navegabilidade do agregado aos cons-
tituintes, uma variável no agregado apontará aos constituintes. Por exemplo,
a classe RelatórioDeGerência poderá conter a seguinte declaração:

parteDoTexto:Conjunto<Parágrafo>;

Outros exemplos de agregação aparecem nas Figuras 4.18 e 4.19:

Figura 4.18 Uma corporação é uma agregação de diversas divisões.

Cada uma dessas agregações tem uma multiplicidade diferente da mos-


trada na Figura 4.17. Uma divisão de corporação pode pertencer a somente
uma corporação, e uma corporação pode conter, no mínimo, uma divisão
(bem... segundo este modelo). Uma linha de ordem pode pertencer a somente
Cap. 4 DIAGRAMAS DE CLASSE 131

uma ordem, mas uma ordem pode até nem conter linhas (talvez se estiver sen-
do construída em vários minutos ou horas). Observe que, em ambos os exem-
plos, estamos utilizando a multiplicidade para anular uma habilidade teórica
que o constituinte possui de pertencer simultaneamente a mais do que um
agregado.

Figura 4.19 Uma ordem é uma agregação de diversas linhas de ordem.

4.4 Resumo
A UML representa a herança com uma seta de generalização que se estende
da subclasse derivada à classe da qual ela foi herdada (superclasse). Essa no-
tação trata tanto da herança simples quanto da herança múltipla (na qual
uma classe é derivada de diversas superclasses). Entretanto, você pode optar
por representar a herança simples como uma listagem endentada de classes.
Para grandes hierarquias, a representação textual pode ser mais concisa; no
entanto, será menos graficamente imediata que na UML.
A UML indica propriedades de particionamento de subclasses com um
termo extraído de cada um destes pares: de disjunção/sobreposição e comple-
to/incompleto. O critério para essa divisão pode ser indicado por um discrimi-
nador de restrições.
A UML modela uma associação binária com uma linha desenhada entre
as duas classes associadas. O nome da associação (usualmente um substanti-
vo) aparece sobre a linha. Cada multiplicidade da associação aparece na res-
pectiva extremidade da linha na forma mín..máx, em que mín e máx são
geralmente exibidos por números inteiros, zero ou um, ou por um asterisco
para muitas delas. O papel de cada classe na associação pode também apare-
cer na extremidade da linha de associação, se ela adicionar significado ao mo-
delo. Você, além disso, poderá caracterizar uma associação com os detalhes de
132 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

implementação da navegabilidade. Isso aparece como uma ponta de seta em


uma ou em ambas as extremidades da linha de associação, indicando o sentido
no qual a navegação será implementada.
Se desejar acentuar o fato de que uma associação é uma classe em seus
próprios termos — talvez para mostrar seus atributos ou operações —, você
pode converter a associação em uma classe ordinária. Você então anexa o sím-
bolo de classe, por meio de uma linha pontilhada, à linha entre as duas classes
associadas. Para associações ternárias e de ordem mais alta, você une as li-
nhas das classes associadas a um diamante. Esse tipo de associação é normal-
mente tratado como uma classe em si, e seu símbolo de classe é unido, via
linha pontilhada, ao diamante.
Cada uma das construções todo/parte de composição e agregação repre-
senta um caso especial de associação. Para composição, a UML adorna a linha
de associação com um pequeno diamante preto na extremidade, junto ao ob-
jeto composto. Para agregação, a UML adorna a linha de associação com um
pequeno diamante aberto na extremidade, junto ao objeto agregado. Para am-
bas, a composição e agregação, você normalmente exibe pontos fundamentais
e papéis e, se for conveniente, navegabilidade. Entretanto o nome da associa-
ção é geralmente suprimido a menos que ele acrescente informação adicional
além da natureza todo/parte do relacionamento.

4.5 Exercícios
1. Os desenhistas da UML poderiam ter unificado os conceitos de divisão
em subclasses, de disjunção/sobreposição e de completo/incompleto, em
um conjunto único de possíveis combinações? No caso de resposta afirma-
tiva, como? (Dica: sob cada uma das quatro combinações entre divisões,
pense sobre o número de subgrupos aos quais o membro de um grupo po-
deria simultaneamente pertencer. As Figuras 4.6 e 4.7 podem lhe ajudar
a visualizar a resposta.)
2. Na Figura 4.10, o discriminador sob o qual dividi VeículoExternamentePro-
pulsionado foi tipoDeVeículo. Entretanto, o discriminador sob o qual dividi
VeículoInternamentePropulsionado foi meioDoVeículo. Essa falta de sime-
tria chega a incomodá-lo?
3. Na Figura 4.10, marquei o particionamento da subclasse VeículoInterna-
mentePropulsionado como de sobreposição. Que implicação isso tem para
o desenho e programação do discriminador meioDoVeículo? Que implica-
ção um particionamento incompleto pode apresentar para o(s) valor(es)
de um discriminador?
Cap. 4 DIAGRAMAS DE CLASSE 133

4. A seção 4.1.4 continha as seguintes palavras:


O particionamento de VeículoInternamentePropulsionado é do tipo incom-
pleto porque estão faltando veículos aéreos e espaciais (entre outros). A
classe VeículoInternamentePropulsionado será presumivelmente desenha-
da como uma classe concreta para capacitar a geração de objetos aéreos
e espaciais.
Sob quais aspectos isso é simplista?
5. Esboce um diagrama de herança de classe que capture as duas categorias
de clientes de uma companhia: clientes externos, representados por ou-
tras companhias, e clientes internos, que são todas as divisões dentro da
companhia.
6. Quando é apropriado modelar utilizando-se da composição? Por exemplo,
por que não utilizar a notação de composição da UML para mostrar que
um cachorro é composto de altura, peso, cor e data de nascimento?
7. Uma característica chamada de composição — sobre a qual li todo o tem-
po mas sem achá-la convincente — é a exclusão em cascata (cascading
delete): quando um objeto composto é excluído, os objetos componentes
também são excluídos. Você pode imaginar uma situação na qual gostaria
de excluir um objeto composto, mas sem deixar de reter os objetos com-
ponentes?
8. Um pão fatiado é constituído de fatias de pão. A associação entre o pão e
suas fatias corresponde a uma composição ou a uma agregação?
9. Esboce um diagrama de agregação de objetos para um capítulo de livro
com a seguinte estrutura: um capítulo compreende diversas seções, cada
uma das quais compreende diversos parágrafos e figuras. Um parágrafo
compreende diversas sentenças, cada uma das quais compreende diver-
sas palavras. (Você pode ignorar a pontuação e não precisa ir mais adian-
te em busca da estrutura de uma figura.)

4.6 Respostas
1. Para postular um conjunto unificado de divisões em subclasses, considere
o seguinte: G como um grupo de coisas e c como uma coisa arbitrária. Su-
ponha que G seja dividido em subgrupos. Portanto:
Um particionamento de completo significa que c deve pertencer, no
mínimo, a um subgrupo de G.
Um particionamento de incompleto significa que c deve pertencer, no
mínimo, a zero subgrupos de G.
134 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Um particionamento de disjunção significa que c pode pertencer, no


máximo, a um subgrupo de G.
Um particionamento de sobreposição significa que c pode pertencer
a muitos subgrupos de G.
Essas quatro combinações de divisões, ou restrições, podem ser resumi-
das com o seguinte:
Incompleto, disjunção: c pode pertencer a 0..1 subgrupos de G.
Completo, disjunção: c pode pertencer a 1..1 subgrupos de G.
18
Incompleto, sobreposição: c pode pertencer a 0..* subgrupos de G.
Completo, sobreposição: c pode pertencer a 1..* subgrupos de G.
18Essas
combinações eloqüentes de propriedades poderiam ser escritas,
respectivamente, como 0..1, 1..1, 0..* e 1..*. Todavia, algumas pessoas não con-
sideram essa notação concisa intuitiva.
2. A falta de simetria me incomoda. Por que os analistas não dividiram os
veículos externamente propulsionados por seus meios? Inversamente, por
que os analistas dividiram os veículos internamente propulsionados por
seus meios?
Talvez os veículos externamente propulsionados baseados em terra te-
nham algo em comum que possa ser decomposto em fatores na mesma
classe, enquanto o mesmo não é verdadeiro para os veículos internamen-
te propulsionados baseados em terra. Esse tipo de nuança deveria ser ve-
rificado com os especialistas no assunto em questão.
3. Conforme vimos na resposta à primeira questão, um particionamento de
sobreposição implica que o discriminador de particionamento pode ter vá-
rios valores (por exemplo, terra e água ao mesmo tempo). Um particiona-
mento de incompleto implica que o discriminador tenha um valor nulo.
Por isso, alguns puristas da UML insistem que os discriminadores so-
mente têm valor em particionamento de disjunção e do tipo completo. Eu
não tenho esses escrúpulos; fico mostrando um discriminador de particio-
namento sempre que penso que explicarei mais claramente a razão para
esse tipo de subdivisão.
4. A assertiva da seção 4.1.4 implica que os objetos veículos espaciais e aé-
reos seriam gerados invocando-se simplesmente VeículoInternamentePro-
pulsionado.Novo. Entretanto, veículos espaciais e aéreos são altamente
especializados, com uma porção de atributos e operações. Esses objetos

18. Como de costume, um asterisco(*) quer dizer “muitos.”


Cap. 4 DIAGRAMAS DE CLASSE 135

vão exigir suas próprias subclasses ou mesmo hierarquias de subclasses.


Objetos veículos espaciais e aéreos seriam então gerados a partir de suas
respectivas subclasses.
Um nível ulterior de ingenuidade é que qualquer hierarquia de classe
para um agrupamento realista de veículos é incrivelmente difícil de se ob-
ter — e ainda mais para a notória hierarquia de subclasse de Cliente.
5. A Figura 4.20 mostra um diagrama de herança de classe para os clientes
internos e externos. A porção mais interessante dessa estrutura é a clas-
se ClienteInterno, que é uma derivação hereditária de DivisãoDaCorporação
e Cliente.

Figura 4.20 Hierarquia de herança para Cliente.

Você poderia refinar esse diagrama de herança de classe introduzindo


outra classe CompanhiaExterna, e depois tendo ClienteExterno como uma
derivação hereditária de Cliente e CompanhiaExterna. Essa abordagem se-
pararia completamente as propriedades dos clientes das propriedades das
entidades corporativas e também aperfeiçoaria a simetria da estrutura
da classe.
6. Você deverá usar a UML para modelar somente composições relevantes,
tais como as baseadas na composição física de coisas reais como planado-
res ou cadeiras. Outras composições significativas são caracterizadas por
diversas camadas, em que os objetos componentes, por si próprios, são
compostos de ainda outros componentes. Uma carta de e-mail serve como
um pequeno exemplo dessa estrutura, conforme mencionado na se-
ção 4.3.1.
Inversamente, embora seja possível, você não utilizaria composição
para mostrar que um cachorro é composto de altura, peso, cor e data de
nascimento. Esses são atributos de um cachorro, não componentes. Se
136 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

você utilizar composição para retratar atributos, seus diagramas não te-
rão muito valor duradouro, e seus modelos parecerão maquinados.
7. Primeiramente, devemos perguntar: o que significa excluir uma cadeira?
Mesmo se desmontar uma cadeira, você ainda poderá querer reutilizar (e
assim não “excluir”) as pernas, o assento ou o espaldar da mesma, para
utilizá-los em outra cadeira. Em geral, você pode ainda estar interessado
nos objetos componentes de um objeto excluído, talvez para compor algo
a mais a partir deles.
Eu encontrei um problema particularmente expressivo com a exclusão
em cascata quando estava trabalhando em uma aplicação que tinha uma
construção todo/parte muito similar a essa da cadeira. Na verdade, para
este exemplo, fingirei que realmente era uma cadeira. Em determinada
aplicação, uma cadeira foi “excluída”, juntamente com suas partes (a ca-
deira foi vendida). Em outra aplicação, entretanto, uma cadeira foi “ex-
cluída” — mas suas partes foram retidas (ela foi desmontada).
Portanto, se seguirmos o princípio de exclusão em cascata nesta apli-
cação, estaremos nas presas de um dilema: uma cadeira seria modelada
como um objeto agregado ou composto? (Como vimos na seção 4.3.1, seria
um objeto composto.)
Alguns autores de livros de UML afirmam que “Um objeto é um objeto
composto se, e somente se, quando ele for excluído, seus objetos compo-
nentes também forem excluídos. De outra forma, ele é um objeto agrega-
do”. Entretanto, eu não acho esse “princípio” útil — ou até mesmo válido!
8. Inicialmente, a associação parece mais uma composição, devido ao fato de
que cada fatia pertence, no máximo, a um pão de cada vez. Mas espere!
A associação se parece mais com uma agregação, pelo fato de que todas
as fatias são semelhantes e podem ser arbitrariamente removidas do pão.
Arghh! Após pensar um pouco (e depois de comer alguns sanduíches), de-
cidi chamá-la de composição. Minha razão: quando eu como o último pe-
daço, não tenho mais um pão. (A propósito, Jim Odell cunhou um termo
especial para esse fenômeno de pão fatiado: composição de porção-objeto).
9. A Figura 4.21 mostra uma possível estrutura agregada para um capítulo
de livro.
O diagrama de agregação de objetos mostra que um capítulo é um con-
junto ordenado de seções. As estruturas para um parágrafo e uma sentença
são similares à estrutura de um capítulo.
Cap. 4 DIAGRAMAS DE CLASSE 137

A estrutura de uma seção é um pouco complicada, pelo fato de que uma


seção compreende uma mistura de parágrafos e diagramas. Uma boa forma de
desenhá-la é criar uma classe chamada ComponenteDeSeção, a partir da qual
Parágrafo e Figura são derivadas por herança. Isso permite que cada compo-
nente de uma seção seja ou um parágrafo ou uma figura.
Note que a Figura 4.21 contém a hipótese de que uma seção aparece em
exatamente um capítulo. Se uma seção pudesse aparecer em vários capítulos,
então haveria uma multiplicidade de 1..* na extremidade do objeto composto
da associação. Revise as outras partes da Figura 4.21 para identificar hipóte-
ses similares.
Note também que eu concedo ao capítulo ter zero seções. Ao passo que
isso é discutivelmente correto — você poderá querer preservar uma versão
nula no início do livro de sua autoria — talvez você considere a alteração des-
sa multiplicidade para 1..*.

Figura 4.21 A estrutura de um capítulo de livro.


D iagramas de interação
entre objetos
DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS
5.Diagramas de Interação entre Objetos
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A té o momento, nesta discussão sobre a UML, consideramos principalmen-


te a estrutura estática do código-fonte orientado a objeto, que fica lexi-
calmente em repouso segundo sua listagem. Agora, nós nos voltamos para a
estrutura dinâmica, de execução ou run-time, de um sistema orientado a ob-
jeto. (Obviamente que as estruturas estáticas e dinâmicas são extremamente
correlacionadas, uma vez que o código lexical compilado constitui aquilo que
realmente será executado).
Central à execução de um programa orientado a objeto é o envio de men-
sagens. Como vimos no Capítulo 1, uma mensagem é a solicitação de um ob-
jeto remetente para a execução de uma operação de um objeto destinatário. O
diagrama de interação entre objetos da UML retrata as mensagens e os argu-
mentos de mensagens que os objetos enviam uns aos outros. Esse diagrama
realiza uma tarefa similar a do gráfico de estruturas nas técnicas estrutura-
das pré-históricas: Ele mostra a estrutura de comunicação em run-time do sis-
tema.
O diagrama de interação entre objetos (freqüentemente referido como
diagrama de interação) é ideal para modelar a estrutura de um simples caso
de uso, que é definido em Jacobson, 1992, como uma “seqüência de transações,
comportamentalmente relacionadas, em um diálogo com um sistema”. Em ou-
tras palavras, um diagrama de interação entre objetos freqüentemente repre-
senta o diálogo entre um usuário e um sistema quando eles executam uma
unidade de trabalho proveitosa, tal como na entrega de um pedido de compra
ou no planejamento operacional de uma máquina.
O diagrama de interação entre objetos pode ser de dois tipos diferentes:
o diagrama de colaboração (abordado na seção 5.1) e o diagrama de seqüência
(tratado na seção 5.2). Os dois tipos de diagrama encerram o mesmo conteúdo

138
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 139

— na verdade, um tipo de diagrama pode ser automaticamente convertido no


outro.
O diagrama de colaboração tende a ter um formato mais livre e flexível
quanto à maneira de ele ser desenhado. Ele também fornece um peso similar
aos relacionamentos estáticos e às mensagens dinâmicas entre objetos. O dia-
grama de seqüência, por outro lado, é mais firmemente organizado e enfatiza
a seqüência temporal.
As mensagens também são apresentadas em dois espécimes básicos: sín-
crona (durante a qual um objeto remetente deve esperar pelo objeto destina-
tário para finalizar a execução) e assíncrona (durante a qual um objeto
remetente não precisa esperar). As seções 5.1 e 5.2 lidam com a notação da
UML para mensagens síncronas, a forma usual de mensagens na maior parte
dos sistemas orientados a objeto. A seção 5.3 examina, por meio da UML, as
mensagens assíncronas, que são importantes em muitos sistemas de tempo
real.

5.1 Diagrama de Colaboração


No diagrama de colaboração da UML, os objetos que interagem por meio de
mensagens aparecem como “caixas” padrões da UML, com cada uma delas
portando o nome de um objeto. Veja, por exemplo, a Figura 5.1:

Figura 5.1 Diagrama de colaboração para a


mensagem flapeEsquerdo.posicionarÂngulo.

Observe que o nome de cada objeto tem a forma nomeDoObjeto:NomeDa-


Classe, no qual o sublinhado enfatiza que estamos tratando com instâncias, e
não classes. Em um diagrama de colaboração, cada objeto é identificado com
o nome que os outros objetos utilizam para enviar-lhe uma mensagem, uma
vez que os objetos não têm realmente os seus próprios nomes. Em outras pa-
lavras, um objeto destinatário adota o nome da variável no objeto remetente
que detém o identificador do objeto destinatário.
140 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

5.1.1 Representando uma mensagem


No diagrama de colaboração, uma mensagem síncrona é representada com
uma pequena seta que aponta de um objeto remetente (sendobj) a um objeto
destinatário (targobj).1 A seta de mensagem é etiquetada com o nome de uma
operação do objeto destinatário, seguida pelos argumentos correntes de entra-
da e saída de dados da operação.
A seta de mensagem repousa ao lado de uma linha que vai do remetente
ao destinatário, representando um caminho de comunicação entre os dois ob-
jetos. Existe geralmente um caminho de comunicação devido ao fato de que há
um vínculo (link) entre sendobj e targobj.2 Entretanto, existem outras manei-
ras de um caminho como esse operar. Por exemplo, sendobj poderia ter rece-
bido o identificador de targobj em uma mensagem anterior, a partir de um
terceiro objeto.
O exemplo simplificado de aplicação da eletrônica na aviação, na Figura
5.1, mostra a operação aterrissar (em um objeto remetente da classe Aeronave)
enviando uma mensagem para um objeto destinatário flapeEsquerdo (da classe
Flape). A mensagem solicita que flapeEsquerdo se posicione em um ângulo de
aterrissagem apropriado. O argumento de retorno ânguloDeAterrissagemOK é
colocado como true todas as vezes que o valor do ânguloDeAterrissagem for vá-
lido. A mensagem que você leria no código da operação do objeto remetente
aterrissar seria:

flapeEsquerdo.posicionarÂngulo (ânguloDeAterrissagem, out


ânguloDeAterrissagemOK)

Lembre-se de que, em um diagrama de colaboração — ou em um diagra-


ma de seqüência, que discutiremos na seção 5.2 —, o nome de um argumento
é o nome real desse argumento, contrariamente ao nome formal utilizado pelo
objeto destinatário em seu título de operação.3 Um exemplo de um nome real

1. Os objetos remetente e destinatário são em geral, mas não necessariamente, objetos diferen-
tes. Igualmente, quase sempre, eles pertencem a classes diferentes.
2. Como você pode recordar do Capítulo 4, um vínculo é uma instância de uma associação. Em
outras palavras, uma associação existe entre classes, enquanto um vínculo existe entre obje-
tos.
3. Um lembrete: os argumentos seguintes à palavra-chave out na lista de argumentos (se hou-
ver) são argumentos de saída (argumentos do destinatário de volta ao remetente). Os outros
são argumentos de entrada (argumentos do remetente ao destinatário).
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 141

de argumento é ânguloDeAterrissagem, na Figura 5.1. (O nome de argumento


formal no interior da operação posicionarÂngulo é provavelmente ânguloDeFla-
peRequerido.)4
A Figura 5.2 é semanticamente idêntica à Figura 5.1. Entretanto, a caixa
à esquerda deste diagrama de colaboração representa uma única operação
(aterrissar), em vez de um objeto integral (aeronave). Penso que esta aborda-
gem é útil por duas razões: A primeira é que ela fornece um início mais orde-
nado ao diagrama na operação cuja execução inicia a colaboração como um
todo. A segunda é que você pode apontar com precisão o fato de que mensa-
gens particulares emanam só de determinada operação, em vez de emanarem
vagamente desde o objeto como um todo.

Figura 5.2 Diagrama de colaboração para a mesma mensagem


(posicionarÂngulo), com a operação recorrente
(aterrissar) mostrada explicitamente.

Para mostrar a que classe a operação aterrissar pertence, qualifico o nome


aterrissar usando a sintaxe nomeDoObjeto: NomeDaClasse.nomeDaOperação, ou
seja, umaAeronave: Aeronave.aterrissar.5
A Figura 5.3 mostra um exemplo de uma aplicação bancária simplificada.
A idéia é transferir alguns fundos de uma conta (contaOrigem) a outra conta
(contaDestino).6 Repare nos diminutos números que aparecem antes das men-
sagens, representando a seqüência em que elas são enviadas.

4. A maioria das pessoas suprime os nomes de classe ao lado dos argumentos de mensagens em
um diagrama de interação entre objetos. Em outras palavras, normalmente escrevo (ângulo-
DeAterrissagem, out ânguloDeAterrissagemOK), preferentemente a (ânguloDe Aterrissa-
gem: Ângulo, out ânguloDeAterrissagemOK: Booleano). Entretanto, para fins de clareza,
incluí nomes de classe nos exemplos da primeira parte deste capítulo.
5. Para distinguir entre a sintaxe de qualificação de nome da sintaxe de mensagem, prefiro uti-
lizar a convenção da linguagem C++ dos dois-pontos duplos (::), e portanto: umaAeronave:
Aeronave::aterrissar. Todavia, a UML prefere o ponto (.) e reserva os dois-pontos duplos para
os componentes de pacotes, que discutirei no Capítulo 7.
6. Você poderia argumentar que, em vez de estar em ContaBancária, transferirFundos, deveria
ser uma operação na classe utilizada na Figura 5.3, uma classe mais específica de aplicação,
assim como Transferência. No decorrer deste capítulo, introduzirei Transferência.
142 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 5.3 Diagrama de colaboração para a


mensagem contaOrigem.transferirFundos.

Tenho de confessar que não sou um grande entusiasta de números em se-


qüência. Minhas razões? A primeira é que eles não me ajudam muito: ou a se-
qüência é bastante fácil de compreender (e assim quem precisa dos números?)
ou ela é difícil (e, portanto, como se atribui uma seqüência temporal significa-
tiva a um algoritmo complicado?). Em vez de acrescentar números seqüenciais
a um algoritmo complicado, você pode especificar a seqüência de mensagens
escrevendo um pseudocódigo para um método — a implementação de uma
operação — um tópico que abordo na seção 5.2, com os diagramas de seqüência.
A segunda razão para que eu não seja um grande entusiasta de números
em seqüência é que eles exigem muito esforço para mantê-los atualizados,
muito embora algumas ferramentas possam ajudá-lo com essa tarefa. No en-
tanto, mesmo dispondo de uma ferramenta, você terá de acrescentar números
seqüenciais a suas mensagens se quiser que sua ferramenta converta automa-
ticamente diagramas de colaboração em diagramas de seqüência.7
A Figura 5.4 mostra outro exemplo simplificado de atividade bancária,
dessa vez uma retirada de dinheiro de uma conta no First Draconian Bank of
Greed, no qual os clientes podem fazer apenas uma retirada por dia de quais-
quer de suas respectivas contas. Portanto, conforme indicado na Figura 5.4, o
objeto contaDoCliente (da classe Conta) atualiza o objeto cliente (representando
o cliente titular da conta) com a data e hora da retirada. Isso é feito invocando
a operação especificar, especificarÚltimaHoraDeRetirada (HoraDeRetirada: DataHora).8

7. Algumas ferramentas utilizam números seqüenciais aninhados hierárquicos para essa finali-
dade. Veja Fowler e Scott, 1997, como exemplo.
8. A propósito, esse não é o único — nem mesmo o melhor — desenho orientado a objeto para
satisfazer as condições da aplicação. Para uma discussão de alternativas, veja o exercício 3
do Capítulo 14.
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 143

Figura 5.4 Diagrama de colaboração para a mensagem


cliente.especificarÚltimaHoraDeRetirada.

A Figura 5.5 mostra outra mensagem entre o mesmo par de objetos. Esta,
na realidade, corresponde à mensagem anterior de contaDoCliente a cliente
para que se encontre a data e a hora da mais recente retirada de dinheiro do
cliente. O objeto contaDoCliente obtém essa informação invocando a operação
obter, últimaHoraDeRetirada: DataHora.

Figura 5.5 Diagrama de colaboração para a


mensagem cliente.últimaHoraDeRetirada.

Há algo contrário à intuição sobre a seta de mensagem da Figura 5.5: A


informação flui do cliente a contaDoCliente, mas a seta de mensagem aponta
da contaDoCliente a cliente. Em outras palavras, a informação para essa ope-
ração obter flui na direção oposta à seta. A razão para isso é que uma seta de
mensagem na UML mostra quem invoca quem, e não “quem tem algo interes-
sante para passar a quem”. Se isso aparenta ser artificial, então talvez você
possa nomear essa operação obter como obterÚltimaHoraDeRetirada para prover
mais clareza.

5.1.2 Polimorfismo no diagrama de colaboração


O polimorfismo empresta uma grande força à orientação a objeto. E agora a
notícia ruim: o polimorfismo e a ligação dinâmica (dynamic binding), que cor-
responde ao mecanismo de implementação em run-time do polimorfismo, tam-
bém causam dores de cabeça aos desenvolvedores de notações. No mundo do
desenho estruturado, nós sempre sabíamos onde estávamos: call A sempre quis
dizer call A. Porém, se for aplicado o polimorfismo orientado a objeto, o objeto
remetente poderá não estar atento da exata classe do objeto destinatário. Em
144 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

razão disso, conseqüentemente, que nome deveria ser dado à classe do objeto
destinatário?
A resposta é esta: Faça da classe do objeto destinatário a classe mais bai-
xa na hierarquia de herança, ou seja, uma superclasse de todas as classes às
quais o objeto destinatário pudesse porventura pertencer. Por exemplo, se a
mensagem é ícone.escala e o objeto destinatário (referenciado por ícone) possi-
velmente da classe Triângulo, Retângulo ou Hexágono, em decorrência o nome
da classe no objeto destinatário seria Polígono (assumindo que Polígono seja a
superclasse direta daquelas três classes).
Alguns locais de trabalho dão ênfase ao polimorfismo de um objeto desti-
natário por meio de parênteses, como na Figura 5.6. Quando a determinação
da exata classe do objeto destinatário puder ser feita com toda a certeza na
época do desenho (o equivalente à ligação estática [static binding]), estes lo-
cais de trabalho exibiriam o nome da classe do objeto destinatário sem parên-
teses. Nos casos em que a classe exata é determinada somente em run-time
(ligação dinâmica [dynamic binding]), eles mostrarão o nome da classe do ob-
jeto destinatário dentro de parênteses.

Figura 5.6 O método para implementar escala será de Polígono


ou de uma de suas subclasses, e será dinamicamente
ligado ao objeto destinatário, ícone.

Outro exemplo de polimorfismo: se tivéssemos x.impressos, em que x pu-


desse apontar para um objeto destinatário das classes planilhaEletrônica, docu-
mentoDeTexto, Cliente, ou para várias outras, então provavelmente seríamos
obrigados a escolher para o nome da classe do objeto destinatário a classe
mais alta de todas, talvez a classe Objeto. Provavelmente não haveria outra
classe que fosse uma superclasse para todas as classes.

5.1.3 Mensagens interativas


Uma mensagem interativa é aquela que é enviada repetidamente, em geral
para cada constituinte de um objeto agregado. Como exemplo de uma mensa-
gem interativa, vamos primeiro assumir que vários ícones sejam exibidos em
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 145

uma área de trabalho (desktop) metafórica de um computador (na sua tela),


conforme mostra a Figura 5.7:

Figura 5.7 A mensagem escala é enviada interativamente,


ou seja, sucessivamente para cada um dos diversos
ícones contidos no atualDesktop (o objeto remetente).

Agora, suponha que, em algum ponto, o desktop necessite ampliar ou re-


duzir todos os seus ícones baseado em algum fator. Para assim proceder, o
desktop simplesmente envia a mesma mensagem — escala (fator: NúmeroReal-
Positivo) — sucessivamente para cada ícone.
A mensagem interativa aparece na UML como uma mensagem normal
com três peculiaridades:

1. A mensagem é prefixada por um asterisco (*), significando, como disse-


mos anteriormente, multiplicidade.
2. O nome da coleção (na Figura 5.7, ícones) aparece em sua posição habi-
tual, mas cada objeto destinatário individual permanece sem nome.
3. O símbolo do objeto destinatário é duplicado — mais uma vez para signi-
ficar multiplicidade.9

Uma mensagem interativa normalmente requer alguma espécie de men-


sagem para que a interação se inicialize dentro do programa. Por exemplo, se
os seus ícones estiverem em uma Lista, você necessitará de uma primeira men-
sagem para obter o cabeça da lista (e, conseqüentemente, a interação solicita-
rá repetidamente pelo ícone seguinte). Normalmente, não mostro essas
mensagens cruzadas (traversal messages) em meus desenhos, muito embora
você certamente possa fazê-lo.
Incidentalmente, o exemplo da Figura 5.7 é uma bonita ilustração do tipo
conjunto/parte de orientação a objeto em trabalho. Um objeto envia a mesma
mensagem para cada objeto dentro de um grupo. Entretanto, uma vez que
cada membro desse grupo talvez tenha uma classe diferente (Hexágono, Círcu-

9. Contudo, quando escrevi uma mensagem iterada como essa em um quadro branco, não me
incomodei com a duplicação do símbolo. Sua ferramenta pode ou não insistir em mostrá-la.
146 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

lo, Desenho e assim por diante), cada um deles pode executar a mensagem a
sua própria maneira especial. Graças ao milagre do polimorfismo, o objeto re-
metente continua, de forma ditosa, inconsciente da sua complicação.

5.1.4 Uso do self (auto) em mensagens


O termo self aparece freqüentemente em mensagens nos diagramas de
UML.10 Self é uma constante de instância (ou seja, não se trata de uma va-
riável) que detém o próprio identificador de um objeto. Isso habilita um objeto
a enviar uma mensagem para:

1. passar self como um argumento, e portanto dizendo ao objeto destinatário


qual objeto enviou a mensagem, ou
2. enviar uma mensagem para si próprio.

Na UML, você mostraria a primeira utilização (passar self como um ar-


gumento) simplesmente nomeando um dos argumentos de mensagem como
self. Veja a Figura 5.8.

Figura 5.8 self passado como um argumento.

Passar self como argumento ocorre mais freqüentemente quando o objeto


destinatário precisa respeitar o identificador do objeto em uma tabela que, tal-
vez, refira o objeto remetente a algum(ns) outro(s) objeto(s). Alguns princi-
piantes na orientação a objeto “borrifam” seus diagramas com argumentos
self, achando que um objeto destinatário precisa do identificador do objeto re-
metente para “de alguma forma conseguir com que os argumentos de saída re-
tornem ao objeto correto”. Nem tanto! A seta de mensagem síncrona implica
o retorno automático do controle de execução, do objeto destinatário até o ob-
jeto remetente. Na verdade, todas as principais linguagens de programação
orientadas a objeto lidam com o retorno do controle ao objeto remetente dessa
forma.11

10. auto não é um termo oficial da UML; é um termo do Smalltalk. Você talvez prefira o termo
de C++ e Java, this, ou o da linguagem Eiffel, Atual.
11. Entretanto, em um chamado mecanismo de recado (callback), o destinatário realmente preci-
sa do identificador do remetente. Eu abordo esse ponto na seção 5.3.2.
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 147

Por isso, utilize self judiciosamente como um argumento. Tome também


cuidado com as “mensagens iô-iô”, em que os objetos remetente e destinatário
continuamente trocam de papéis e se enredam mutuamente em um diálogo de
mensagens aparentemente interminável! Desconfia-se disso em um diagrama
de interação entre objetos quando se vêem os argumentos self “voando” de um
lado para outro como “petecas”.
Na UML, você poderia mostrar a segunda utilização de self (quando um
objeto envia uma mensagem para si próprio) nomeando o objeto destinatário
de self, conforme mostrado na Figura 5.9.12

Figura 5.9 Objeto remetente enviando uma mensagem


a si próprio, em que self é o objeto destinatário.

A Figura 5.10 mostra um estilo alternativo, mais evocativo, por meio do


qual a linha de referência retorna para ela mesma e tem o estereótipo «self».

Figura 5.10 A linha de referência retorna para o remetente.

Em termos estritamente de programação, uma mensagem de um objeto


para ele mesmo talvez não seja necessária. Se o método do remetente e o mé-
todo do destinatário pertencem ao mesmo objeto, o método do remetente pode
diretamente manipular as mesmas variáveis do método do destinatário, se ti-
ver acesso a todas as variáveis do objeto.

12. Conforme você pode ver da Figura 5.9, é aceitável repetir símbolos de objetos em um diagra-
ma de colaboração. Essa repetição pode ajudar se as linhas começarem a se cruzar ou a fazer
curvas de maneira muito fechada (como na Figura 5.10). No restante deste capítulo, a esse
respeito, eu não mais incluirei nomes de classe ao lado de argumentos em mensagens. A maio-
ria das ferramentas também suprimem nomes de classe em argumentos reais, mostrando-os
somente em argumentos formais.
148 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Entretanto, essa abordagem tem dois inconvenientes. Primeiro, ela pode


duplicar certo código pelos dois métodos. Segundo, ela dissemina o conheci-
mento da representação das variáveis a muitos métodos diferentes. O fato de
ter um objeto enviando uma mensagem para ele mesmo é, em razão disso, útil
quando um desenhista quer usar a idéia-chave da ocultação de implementação
para esconder a implementação de uma operação de outra, na mesma classe.
Portanto, algumas classes são desenhadas com “anéis de operações”, tópico ao
qual retornaremos no Capítulo 13.

5.2 Diagrama de Seqüência


O diagrama de seqüência é um tipo de diagrama de interação entre objetos
que enfatiza mais a seqüência temporal que os relacionamentos estáticos do
objeto. Eu prefiro o diagrama de seqüência ao diagrama de colaboração quan-
do existe mais ou menos meia dúzia de mensagens “fofocando” entre objetos
em um grupo; com o diagrama de seqüência, eu consigo ver claramente “quem
diz o que para alguém e quando”. Também, certamente prefiro isso sempre
que questões relativas a procedimentos ou sincronia (timing) se tornarem
complicadas, pelo fato de que nenhum outro diagrama esclarece a sincronia
tão bem.
Conforme mostrado pelo diagrama de seqüência na Figura 5.11, o tempo
aumenta no sentido decrescente ao longo do eixo vertical, e uma listagem de
objetos, que receberão mensagens, estende-se ao longo do eixo horizontal — na
parte superior. Esses objetos podem ser listados por seus nomes de classe, se
isto for realmente preciso. O corpo do diagrama mostra as operações ativadas,
dimensionadas para refletir suas durações aproximadas: o símbolo para uma
operação ativada é aproximadamente proporcional verticalmente ao período
de tempo durante o qual a operação está ativa. As setas entre operações re-
presentam mensagens, da mesma forma que no diagrama de colaboração.13
Os números seqüenciais opcionais nas mensagens comunicam a seqüência
temporal; igualmente ao que acontece no diagrama de colaboração.
Agora, vamos examinar o que ocorre na Figura 5.11, que contém outro
exemplo de transferência bancária.14 Permita que eu rapidamente mostre o
caminho da seqüência de execução de mensagens.

13. Da mesma forma que no diagrama de colaboração, as setas apontam unidirecionalmente no


sentido da operação invocada pela mensagem. Em uma mensagem síncrona, o controle retor-
na ao remetente da mensagem após a operação invocada ter sido executada, mas a notação
trata o retorno como implícito.
14. Esse exemplo de transferência de fundos é mais realista do que aquele mostrado na Figura
5.3. Agradecimentos a Ken Boyer, por ele ter arranjado o diagrama.
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 149

Figura 5.11 Diagrama de seqüência para transferência de fundos.

A seqüência inicia com o não-nomeado objeto Transferência à esquerda.


Uma operação desse objeto, efetuarTransferência (exibida como a longa
barra vertical à esquerda), executa a transferência de fundos de uma conta
(contaOrigem) a outra conta tida pelo mesmo cliente (contaDestino). Eu supo-
nho que outra operação do objeto Transferência inicializou o objeto com infor-
mações, tais como as contas envolvidas, a quantia de dinheiro e assim por
diante.
A operação efetuarTransferência envia uma mensagem nova para criar um
objeto a fim de monitorar a transação, nomeada transferirAçãoX da classe
TM.15 A operação efetuarTransferência, em razão disso, envia uma mensagem
iniciar para inicializar transferirAçãoX. Nós precisamos que a transação seja
monitorada para que, em caso de acontecer alguma falha, o sistema desfaça
quaisquer alterações parcialmente completadas, conforme indica a mensagem
desfazerOperações. Não queremos retirar dinheiro de uma conta e fracassar no
processo de depositá-lo em outra, mesmo que o gerente do banco considere isso
uma idéia excelente!
As duas mensagens seguintes destinam-se, respectivamente, a contaOri-
gem: ContaBancária e contaDestino: ContaBancária, evocando o titular da conta.

15. Algumas ferramentas mostram a geração de objetos com a seta de mensagem tocando a pró-
pria caixa de objeto.
150 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O objeto Transferência precisa saber quem são os titulares das contas por duas
razões: para verificar que as duas contas têm o mesmo titular e para se intei-
rar da reputação do titular (com graduações como boa, má, desprezível, esgo-
tada ou indigna).
A próxima mensagem, retirarFundos (quantia, out retiradaOK), vai para
contaOrigem: ContaBancária, que envia a mensagem titularDaConta.saldoMíni-
moPermitido para certificar-se de que a retirada não reduziria o saldo da conta
a uma quantia abaixo do saldo mínimo que o cliente precisa manter. Conforme
determinado pelo pseudocódigo na caixa de descrição lateral, o argumento de
saída de retirarFundos (quantia, out retiradaOK) será colocado como true se tudo
estiver bem.
Finalmente, se tudo realmente estiver bem, a mensagem depositarFundos
(quantia, out depósitoOK) incrementará o dinheiro mantido em contaDestino, e
a mensagem registrar informará à transferirAçãoX que é inteiramente satisfa-
tório registrar irrevogavelmente todo o conjunto de atualizações. Se algo não
sair a contento, a mensagem desfazerOperações informa à transferirAçãoX que
ela deverá descartar quaisquer alterações ocorridas desde iniciar. (A propósito,
não tenho certeza de como depósitoOK poderia um dia tornar-se falso — talvez
somente quando a quantia for negativa.)
A maior parte do diagrama de seqüência é compreensível intuitivamente,
mas ele realmente apresenta algumas limitações. No diagrama de seqüência
da Figura 5.11, não fica claro que as mensagens desfazerOperações e registrar
sejam mutuamente exclusivas; uma é enviada, mas não ambas.
Dessa forma, para onde as nuanças do algoritmo irão? Formalmente, elas
fazem parte, na condição de pseudocódigo, de um ou mais métodos. Informal-
mente, você pode anexar essas caixas de descrição aos diagramas de seqüência
como se fossem recados adesivos, e algumas ferramentas dispõem de uma fa-
cilidade para isso. Se você está usando um diagrama de seqüência para mo-
delar um caso de uso, você pode colocar a especificação textual do mesmo na
caixa à esquerda. (Veja Rosenberg e Scott, 1999, para maiores detalhes.)
Nesse exemplo de diagrama de seqüência, os nomes junto à parte supe-
rior do diagrama referem-se a objetos. Alternativamente, você pode utilizar
nomes de classes, como mencionei anteriormente, ou você pode colocar tarefas
ou processadores inteiros junto ao topo do diagrama para modelar o compor-
tamento da execução dos artefatos arquiteturais de um sistema.16

16. Neste livro, eu tendo a utilizar a palavra artefato para representar um fragmento de tecno-
logia (tal como uma tarefa de processador ou banco de dados), que é capaz de executar ins-
truções de software ou reter dados.
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 151

5.3 Mensagens Assíncronas e Execução Concorrente


Nas seções 5.1 e 5.2, assumi que todas as mensagens eram síncronas — isto
é, que elas eram processadas pelo objeto destinatário, uma de cada vez, en-
quanto o objeto remetente aguardava. Eu também assumi que toda execução
em um sistema orientado a objeto é de encadeamento único, significando que
apenas um objeto é ativo de cada vez. Para se tornar isso mais explícito, es-
tabeleceu-se o seguinte:

• Apenas um objeto em um sistema pode enviar uma mensagem em de-


terminado tempo.
• O objeto remetente deve esperar até que o objeto destinatário proces-
se a mensagem.
• O objeto destinatário processará somente uma mensagem de cada
vez.

Embora essa seja a forma de muitos ambientes OO predominantes ope-


rarem atualmente, a atividade de passar mensagens síncronas é simplesmen-
te um caso especial da execução entre objetos em geral. Nesta seção exploro o
caso mais geral, a mensagem assíncrona, que permite ao objeto remetente da
mensagem continuar executando, enquanto o objeto destinatário estiver pro-
cessando a mensagem. As mensagens assíncronas demandam vários encadea-
mentos de controle no sistema, para que diversos objetos executem ao mesmo
tempo. Isentos de vários encadeamentos de controle (ou seja, sem concorrên-
cia), todas as mensagens teriam de ser síncronas.17

17. Uma vez que esta seção trata da modelagem de concorrência e mensagens assíncronas na
UML, não me estenderei muito nos mecanismos específicos pelos quais a concorrência pode
ser implementada (tal como envios de mensagens não bloqueadas), mas mencionarei aqui al-
guns termos. Se o sistema como um todo apresenta concorrência, mas cada objeto destinatário
manipula somente uma mensagem de cada vez, há uma concorrência em nível de sistema
(system-level concurrency). Se um objeto destinatário pode manipular várias mensagens de
cada vez, há uma concorrência em nível de objeto (object-level concurrency). Se uma única ope-
ração pode manipular várias mensagens de cada vez, há uma concorrência em nível de ope-
ração (operation-level concurrency). (Uma operação poderia processar simultaneamente
diversas mensagens tendo diversos encadeamentos de execução por meio do código reentrante
ou, alternativamente, o sistema poderia simplesmente processar diversas cópias da operação.)
Um processador pode igualmente simular concorrência (denominada pseudoconcorrência) por
meio de, digamos, “repartimento de tempo” (time-slicing). Veja Booch, 1994, ou Atkinson,
1991, por exemplo, para descobrir mais sobre as várias formas de concorrência.
152 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

5.3.1 Representando uma mensagem assíncrona


Você pode mostrar mensagens assíncronas em qualquer forma de diagrama de
interação entre objetos (colaboração ou seqüência), utilizando na seta de men-
sagem assíncrona uma meia ponta de seta, conforme ilustra a Figura 5.12.
Quando uma mensagem é enviada, pelo menos dois locais de execução estão
ativos no sistema, devido ao fato de que o objeto destinatário inicia a execução,
enquanto o objeto remetente permanece em execução.

Figura 5.12 Diagrama de colaboração mostrando


uma mensagem assíncrona básica.

A Figura 5.13 mostra um exemplo típico com mensagens assíncronas e


síncronas. Ele é proveniente de um sistema em tempo real autorizante, proje-
tado para que funcionários passem através de portas controladas eletronica-
mente. (O funcionário insere um cartão de identificação em um dispositivo de
leitura, e, se o mesmo é autorizado a entrar, o sistema toca uma música, com
melodia simples e constante, emitindo um cumprimento como “Queira entrar,
sr. Pacheco”, e abre a porta corrediça.)
A operação que controla esta aplicação é permitirEntrada (definida na clas-
se GerenciadorDeEntrada), que aparece à esquerda da Figura 5.13. Após deter-
minar que foi concedida autorização ao funcionário para que ele passasse pela
porta (por meio de uma linha de código dentro de permitirEntrada, que não é
mostrada na Figura 5.13), permitirEntrada envia a mensagem síncrona mante-
rAberta para o objeto porta (uma instância de PortaSegura).
A operação manterAberta, em seguida, manipula o som, as luzes e a ação
associada com a abertura da porta. Para a ação de abrir a porta, manterAberta
envia mensagens para um driver de hardware (por brevidade, essas mensa-
gens não são mostradas na Figura 5.13). Para o show de som e luz, manterA-
berta envia duas mensagens assíncronas, ambas nomeadas como operar: uma
para sonorizadorDaPorta e outra para sinalDaPorta. (É conveniente ter uma
operação de PortaSegura para cuidar das operações do sonorizador e do sinal,
porque cada objeto PortaSegura presumivelmente conhece melhor a configura-
ção de seu sonorizador e sinal.) As duas operações nomeadas como operar exe-
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 153

cutam concorrentemente; o objeto porta, confiando que o hardware e o firm-


ware se comportem corretamente, já não prestará mais atenção neles.
Uma vez que a mensagem para manterAberta era síncrona, permitirEntra-
da espera que manterAberta termine sua execução e, a seguir, envia a mensa-
gem síncrona registrarEntrada para registro.funcionários, que registra a
passagem do funcionário pela porta.

Figura 5.13 Diagrama de seqüência para objetos executando concorrentemente.

5.3.2 O mecanismo de recado (callback)


No maravilhoso romance existencialista A Legion of Disasters (Uma Legião de
Infortúnios), de Eric Lurch, o personagem Philippe se reclina e fala a seu ca-
marada legionário, Ferdinand, deitado na parte de baixo do beliche:
Eu consigo suportar qualquer situação, mas não agüento mais
essa maldita espera. Eu preciso dormir um pouco. Acorde-me
quando os camelos adentrarem na cidade.18
Este trecho de diálogo ilustra o mecanismo de recado (callback), uma utili-
zação comum das mensagens assíncronas na qual ocorrem os seguintes eventos:

18. Veja Lurch, 1972.


154 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

1. O objeto signatário subscrobj registra um interesse em algum tipo de


evento via uma mensagem assíncrona ao objeto destinatário listenobj. (No
exemplo anterior, Philippe registra um interesse no tipo de evento “os ca-
melos adentrando na cidade” para seu companheiro Ferdinand.)
2. O objeto signatário subscrobj continua com outras atividades enquanto
listenobj monitora a ocorrência de um evento do tipo registrado. (Philippe
tira uma soneca, enquanto Ferdinand vigia a chegada dos camelos.)
3. Quando um evento do tipo determinado ocorre, listenobj devolve uma
mensagem (geralmente assíncrona) para subscrobj, notificando-o da ocor-
rência. Isso é o callback. Listenobj pode portanto continuar com outras ati-
vidades. ("Ferdinand gritou: ‘Acorde, Philippe, acorde! Os camelos
chegaram e nós não podemos nos atrasar como da última vez’. A seguir,
sem esperar para ver se Philippe tinha ou não escutado, ele começou a
colocar suas botas.")

Caso você tenha esquecido de seus dias na Legião Estrangeira, a Figura 5.14
expressa o mecanismo de recado de uma forma mais orientada a objeto. Veja
aqui a descrição detalhada:

1. subscrobj envia uma mensagem assíncrona registrarEventoDoTipo1 para


listenobj com o argumento detalhesAdicionais. (Esse argumento nem sem-
pre é necessário, mas ele pode ser utilizado, por exemplo, como um filtro
a mais em eventos interessantes.) O signatário também fornece seu iden-
tificador (via self), o que proverá o endereço de retorno para um callback
posterior.
2. A operação de subscrobj que enviou a mensagem assíncrona registrarEven-
toDoTipo1 continua executando durante algum tempo e então termina.
listenobj registra (talvez em uma tabela) que subscrobj está interessado
em uma ocorrência de evento do tipo 1.
3. Quando um evento do tipo 1 ocorre, listenobj envia a mensagem assíncro-
na eventoDoTipo1Ocorreu para subscrobj, com o argumento detalhesDoE-
vento. (Este argumento nem sempre é necessário: Ele pode conter
informação sobre o específico evento ocorrido.) listenobj então continua
com suas atividades, que muitas vezes incluem dar recados a vários ou-
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 155

tros objetos signatários que registraram interesse em uma ocorrência


desse tipo de evento.19

Figura 5.14 O mecanismo de recado (callback) via


mensagens assíncronas em um meio concorrente.

O diagrama de seqüência na Figura 5.15 mostra um exemplo do mecanis-


mo de recado (callback), no qual o evento de interesse é a chegada de e-mails
com uma urgência acima de determinado limiar.20
O objeto sessãoDoUsuário registra seu interesse em novo e-mail com o ob-
jeto listenerDeE-Mails enviando a mensagem registrarEventoDeNovoE-Mail (clas-
sificaçãoDeUrgência). Aqui, estou assumindo que um listenerDeE-Mails “traba-
lha para” somente uma sessãoDoUsuário e que sessãoDoUsuário já tinha passa-
do self, identificaçãoDoUsuárioDoE-Mail, e assim por diante, quando ele instan-
ciou e inicializou listenerDeE-Mails. Quando um e-mail suficientemente urgente
chega, listenerDeE-Mails chama novamente sessãoDoUsuário com novoE-MailRe-
cebido (máximaUrgência), cujo argumento indica a máxima urgência entre as
mensagens que realmente chegaram. O objeto sessãoDoUsuário então abre
uma janela (acessarE-MailRecebido), que exibe a seguinte mensagem:
Ei amigo! Você recebeu uma nova correspondência com urgên-
cia máxima do presidente da Ruritânia às 12h12 de 02/fev/2001.

19. Observe que, embora listenobj não saiba coisa alguma sobre os objetos signatários (à parte
de seus identificadores), ele deve acreditar que cada um deles tem a operação eventoDoTi-
po1Ocorrido, a qual é requerida para receber e entender a mensagem de recado (callback).
Mesmo assim, esses objetos signatários podem ser de muitas classes diferentes, e podem con-
ter definições e implementações da operação eventoDoTipo1Ocorrido, que são polimorfica-
mente diferentes para cada caso.
20. Eu mostro aqui apenas as mensagens mais importantes para o mecanismo de recado (call-
back), omitindo quaisquer mensagens de geração e inicialização porventura necessárias.
156 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 5.15 Mecanismo de recado (callback) utilizado para detectar e-mails.

O diagrama de seqüência da Figura 5.16 mostra um objeto “listener” que


não apenas espera pelo acontecimento de um evento — ele configura-o para
que aconteça. (Eu coloquei listener entre aspas porque, neste caso, ele não pre-
cisa prestar atenção; ele é o responsável pela ocorrência do evento.)
A tarefa do fragmento de sistema mostrado na Figura 5.16 é conseguir
que um usuário tenha acesso a um sistema de computador remoto. Primeira-
mente, o objeto sessãoDoUsuário (da classe Sessão) envia uma mensagem as-
síncrona estabelecerComunicação para um objeto destinatário portaConectada
(da classe Porta), pedindo para estabelecer comunicação com o computador re-
moto. Uma vez que o estabelecimento dessa comunicação talvez demore certo
período de tempo, o objeto sessãoDoUsuário pode prosseguir com qualquer ou-
tra inicialização requerida pela sessão.
Quando a operação estabelecerComunicação tiver finalizado sua tarefa —
logo que o computador remoto iniciar a comunicação — ele enviará uma men-
sagem de callback assíncrona comunicaçãoEstabelecida (identificaçãoDoServi-
dor, comunicaçãoOK) para sessãoDoUsuário. (Aqui, comunicaçãoOK é
posicionada como true somente se uma conexão com um servidor na máquina
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 157

remota for atingida com êxito.) A seguir, o objeto sessãoDoUsuário pode acessar
a máquina remota, talvez obter autorização remota para banco de dados e as-
sim por diante.

Figura 5.16 Mecanismo de recado (callback) utilizado para


conectar uma sessão a um computador remoto.

Existem duas diferenças entre este modo de callback e aquele mostrado


na Figura 5.15. A primeira é que o objeto listener não repousa preguiçosamen-
te, esperando de forma passiva pelo acontecimento de algum evento. A segun-
da é que o evento de interesse ocorre somente uma vez. Portanto, não há
registro formal de interesse em um tipo de evento. Na verdade, a única razão
para um mecanismo de callback (com suas duas mensagens assíncronas), em
vez de uma única mensagem síncrona desde sessãoDoUsuário a Porta, é esta:
sessãoDoUsuário pode utilizar a concorrência para proceder com outras ativi-
dades, que, de forma diferente, teriam de ser executadas em série.
158 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

5.3.3 Mensagens assíncronas com prioridade


Ao aceitarmos a concorrência, também permitimos que um objeto destinatário
seja bombardeado por mensagens de vários objetos remetentes executando
concorrentemente. Uma vez que essas mensagens podem chegar mais rápido
do que o objeto destinatário consegue processá-las, elas terão de ir a algum lu-
gar para esperar pela vez delas. Elas vão para uma “sala de espera”, mais fre-
qüentemente conhecida como uma “fila de mensagens”.
O objeto destinatário anuncia a chegada de mensagens na fila de mensa-
gens. O objeto, em razão disso, remove repetidamente uma mensagem da fren-
te da fila, invoca a operação que processará essa mensagem e recolhe a
mensagem seguinte da fila. As mensagens que transbordam a fila são rejeita-
das. Um objeto destituído dessa flexibilidade de formação de filas rejeitará to-
das as mensagens que não consigam ser imediatamente processadas. (As
mensagens rejeitadas podem ser devolvidas ao objeto remetente com um indi-
cador de exceção.)
As mensagens em uma fila podem ser ordenadas por prioridade. Você
pode retratar isso como um conjunto de filas paralelas ao objeto destinatário,
cada uma delas com o seu próprio nível de prioridade, conforme mostrado na
Figura 5.17.

Figura 5.17 Três filas paralelas, cada uma com sua própria prioridade.

A Figura 5.18 mostra como eu anoto um mensagem assíncrona com seu


nível de prioridade. Aqui, como parte de um sistema de e-mails, uma mensa-
gem assíncrona está sendo enviada ao objeto portaDeE-Mail, pedindo para que
o mesmo transmita alguma mensagem de partida. Às vezes, as mensagens de
e-mail chegam em portaDeE-Mail mais rápido do que elas conseguem sair, e
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 159

portanto elas têm de se dirigir para uma fila de prioridade múltipla. A pro-
priedade {prioridade = 3}, ao lado da seta de mensagem, indica que a mensa-
gem na Figura 5.18 tem uma prioridade igual a 3.21

Figura 5.18 Mensagem assíncrona (com prioridade 3), indo para um


objeto com uma fila de mensagem de prioridade múltipla.

5.3.4 Representando uma mensagem de difusão


(broadcast) [nontargeted]
Quando um objeto envia uma mensagem, ele normalmente detém o identifi-
cador do objeto destinatário da mensagem. Entretanto, em sistemas com con-
corrência e mensagens assíncronas, isso nem sempre é verdadeiro. No caso
extremo, um remetente pode transmitir uma mensagem — isto é, tratar cada
objeto no sistema como um destinatário em potencial. Uma cópia da mensa-
gem vai para a fila de todos os objetos no sistema.
Um objeto pode transmitir uma mensagem de “a quem possa interessar”
em resposta a algum evento externo que for detectado. Por exemplo, um objeto
poderá detectar algum compromisso de segurança e transmitir a todos os ob-
jetos a necessidade de uma paralisação prioritária do sistema. Um objeto tal-
vez ignore uma mensagem de difusão em razão de não possuir qualquer
operação com a qual possa processá-la. Um outro objeto talvez opte por igno-
rar a mensagem temporariamente até que ele atinja um estado apropriado.22

21. Embora neste caso eu mostre mensagens com prioridades, não discuti exatamente como a im-
plementação das prioridades de mensagens poderia trabalhar. A abordagem mais simples é
ter um objeto processando suas mensagens na ordem descendente de prioridade. Os objetos
remetentes também podem ter níveis de prioridade. Sendo assim, uma regra do tipo “Uma
mensagem não poderá ter uma prioridade maior que a do objeto remetente” fará com que par-
tes de um sistema obtenham uma prioridade de execução garantida. Por exemplo, se um rea-
tor nuclear está prestes a “fundir”, uma boa idéia pode ser assegurar que o software de
resfriamento do mesmo seja executado antes do software do relatório funcional mensal. Já
que em muitos ambientes as prioridades são definidas como propriedades de tarefas, uma
operação pode arrendar suas mensagens a uma de um grupo de tarefas similares, cada uma
delas operando com uma diferente prioridade. Mas essas simples abordagens não lidam com
problemas de prioridade particularmente desagradáveis, tais como fome e inversão de priori-
dades. Veja Sha e outros, 1990, Lampson e Redell, 1980, ou Grehan e outros, 1998, para de-
talhes adicionais.
22. Eu trato dos estados de objeto nos Capítulos 6 e 10.
160 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Porém, a maioria dos objetos que compreenderem a mensagem começarão a


agir imediatamente após o recebimento da mesma.
A Figura 5.19 mostra por meio da UML uma mensagem de difusão, na
qual utilizo o estereótipo «broadcast» para indicar a natureza de difusão dessa
mensagem. Nesse exemplo, um seqüenciador de configuração (start-up se-
quencer) está alcançando todos os objetos no sistema que existem na hora da
configuração para carregar-se a si mesmo. (Talvez isso signifique “Carregue
suas variáveis a partir das informações armazenadas”, processo esse às vezes
chamado reidratação de objeto [object reyydration]).

Figura 5.19 Mensagem de difusão (broadcast).

Uma mensagem de difusão (broadcast) é similar a uma mensagem inte-


rativa pelo fato de ela ir para muitos objetos — por isso o símbolo duplo no
remetente (se você optar por usá-lo) e o prefixo de asterisco (*) na mensagem.
(Incidentalmente, Muller, 1997, gosta de usar o símbolo *|| para enfatizar a
natureza paralela de uma transmissão verdadeira.) Já que o nome do objeto
destinatário, Objeto, implica que os destinatários são objetos da classe Objeto,
ou de suas subclasses, a mensagem sai para todos. Algumas pessoas preferem
utilizar o nome de classe Qualquer, para indicar que o destinatário é todas as
classes e objetos.
Na aplicação de tempo real mostrada na Figura 5.20, uma operação do
objeto não nomeado : MonitorDoPainelDeInstrumentos (talvez uma operação
chamada sinalDeTempoParaRenovarMostradores) “observa um relógio” para de-
terminar quando é a hora para renovar os mostradores em um painel de ins-
trumentos de um veículo. O nome da classe do destinatário indica que
somente objetos da classe Instrumento (e suas subclasses) obterão a mensa-
gem. Eu denomino essa transmissão seletiva de difusão restrita (narrowcas-
ting). Como vimos preliminarmente, por meio do polimorfismo, cada objeto de
uma subclasse de Instrumento (tal como Velocímetro ou Tacômetro) pode exe-
cutar sua própria versão da operação exibirLeituraAtual.
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 161

Figura 5.20 Mensagem de difusão restrita (narrowcasting).

Para comparação com a Figura 5.20, mostro na Figura 5.21, por meio da
UML, uma mensagem interativa padrão (de não-transmissão), em que o obje-
to : MonitorDoPainelDeInstrumentos detém os identificadores de todos os ins-
trumentos relevantes (em uma Lista ou Conjunto denominado de instrumentos).
Desta vez, naturalmente, não há o estereótipo «broadcast» na mensagem.

Figura 5.21 Mensagem interativa.

5.4 Resumo
Este capítulo considerou os dois tipos de diagrama de interação entre objetos
da UML: o diagrama de colaboração e o diagrama de seqüência. Muito embora
ambos os tipos retratem a interação de mensagens entre objetos durante a
execução do sistema, cada tipo tem suas próprias características especiais.
O diagrama de colaboração é mais flexível no tocante à forma e revela se-
qüências temporais de mensagens só numericamente. Ele representa conjun-
tamente os relacionamentos estáticos de objetos e as mensagens dinâmicas
entre estes. O diagrama de seqüência mostra as seqüências temporais de men-
sagens graficamente, dessa forma útil para clarificar a seqüência de intera-
ções entre objetos se comunicando. Entretanto, o diagrama de seqüência não
representa os relacionamentos estáticos de objetos.
Os objetos são mostrados nos diagramas de colaboração como caixas, com
o nome de cada objeto expresso como nomeDoObjeto: NomeDaClasse, em que
o sublinhado enfatiza a natureza ilustrativa do símbolo. Se um objeto é o filho
único de sua classe, você pode deixá-lo sem nome, sob a forma : NomeDaClas-
162 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

se. O nome de um objeto destinatário é geralmente escolhido como o nome pelo


qual o remetente se refere ao objeto destinatário.
O polimorfismo complica o processo de nomeação da classe do objeto des-
tinatário, em razão do objeto remetente ou, na verdade, até mesmo você, o de-
senhista — talvez desconheça precisamente a classe do objeto destinatário.
Com destinatários polimórficos, opte pelo nome da classe mais baixa na hie-
rarquia de herança, ou seja, a superclasse de todas as classes às quais o objeto
destinatário poderia pertencer. Se desejar, você pode realçar o polimorfismo
colocando esse nome entre parênteses.
Uma mensagem em um diagrama de colaboração é uma seta ao lado da
linha que retrata o vínculo (instância de associação) entre os objetos remeten-
te e destinatário. A seta é etiquetada com o nome da operação a ser invocada
no objeto destinatário, junto com os argumentos reais de entrada e saída de
dados para a operação. Uma mensagem interativa é prefixada com um aste-
risco (*) seguida pela seqüência de interação em colchetes, isso se ela for im-
portante. Para enfatizar a interação, você também pode duplicar a caixa do
objeto destinatário.
O diagrama de seqüência retrata as interações entre objetos com o tempo
percorrendo de norte a sul no diagrama. Cada objeto aparece como uma linha
pontilhada vertical com o nome do objeto na ponta principal. Cada execução
de uma operação do objeto aparece como uma caixa fina sobre a linha ponti-
lhada, de comprimento aproximadamente proporcional ao tempo de execução
da operação. As mensagens entre objetos são apresentadas como linhas hori-
zontais que conectam uma caixa de execução à outra. Você poderá realçar os
algoritmos por métodos como os de anexar o pseudocódigo ao diagrama de se-
qüência. Os diagramas de seqüência têm as mesmas convenções de atribuição
de nomes que os diagramas de colaboração.
Tanto o diagrama de colaboração quanto o diagrama de seqüência podem
exibir mensagens síncronas, nas quais o objeto remetente espera para que o
destinatário complete a execução, e mensagens assíncronas, nas quais o objeto
remetente continua a execução após o envio da mensagem. A UML mostra
mensagens síncronas com uma ponta cheia de seta e mensagens assíncronas
com uma meia ponta de seta.
As mensagens assíncronas são geralmente utilizadas para implementar
o mecanismo de recado (callback), no qual um objeto signatário (subscriber)
envia uma mensagem assíncrona para um objeto listener para registrar seu
interesse em um tipo de evento. O objeto listener, em razão disso, envia uma
mensagem assíncrona ao signatário quando ocorre um evento do tipo prescrito.
Cap. 5 DIAGRAMAS DE INTERAÇÃO ENTRE OBJETOS 163

O processo de mensagens assíncronas requer algum tipo de concorrência


de execução no sistema. Como conseqüência, as mensagens podem, às vezes,
precisar enfileirar-se junto a um objeto e ser priorizadas quanto à execução.
Você pode mostrar a prioridade de uma mensagem como uma propriedade da UML.
Em uma mensagem de difusão (broadcasting), o objeto remetente talvez
não tenha os identificadores dos objetos destinatários, mas envia as mensa-
gens para todos os (ou, no tipo restrito [narrowcast], para alguns) objetos que
possam entendê-las. A mensagem de difusão é prefixada com um asterisco (*)
e estereotipada com «broadcast». A classe do objeto destinatário é (Objeto) ou
(Alguma).23 Uma mensagem do tipo narrowcast tem um destinatário de uma
classe mais específica.

5.5 Exercícios
1. Trace um diagrama de seqüência (ou colaboração) para uma operação de
sua escolha. (No meu caso, escolhi o algoritmo do Capítulo 1 para a na-
vegação do hominóide pela grade, que coloquei em uma operação de uma
nova classe, denominada Navegador.)
2. Para a classe que você escolheu no exercício 1, existe alguma concorrên-
cia possível em nível de objeto? Que operações poderiam executar simul-
taneamente no mesmo objeto? Existem quaisquer pares de operações que
você não desejaria executar simultaneamente no mesmo objeto?
3. Neste capítulo, sutilmente dei a entender que, quando um objeto reme-
tente detém os identificadores de seus objetos destinatários, ele remete
várias mensagens em série (interativamente). Alternativamente, quando
um remetente não detém os identificadores de seus objetos destinatários,
e emite uma mensagem de difusão, ele remete diversas mensagens em
paralelo. Será que essas duas implicações estão totalmente corretas?

5.6 Respostas
1. A Figura 5.22 mostra o diagrama de seqüência para a navegação do ho-
minóide pela grade até o quadrado final. (Estabeleço que esse é um dia-
grama de seqüência para a implementação do método de uma operação
definida em uma classe Navegador.) Exibo parte do algoritmo na caixa à
esquerda; na prática, provavelmente, você exibiria todo o algoritmo se
sua ferramenta suportasse tal capacidade.

23. Apenas como lembrete, o sinal de parênteses indica polimorfismo na UML. Por exemplo, (Ob-
jeto) significa a “classe Objeto ou uma subclasse de Objeto”.
164 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 5.22 Diagrama de seqüência para a navegação do hominóide.

2. Em Hominóide, as operações get apontandoParaParede e posição (por


exemplo) poderiam executar juntas, porque nenhuma dessas operações
de acesso mudam o estado de um hominóide ou de algo mais. Entretanto,
deveriam existir algumas restrições no tocante à concorrência. Eu duvido,
por exemplo, que os pares de operação virarÀEsquerda/virarÀDireita ou ca-
minhar/virarÀEsquerda pudessem executar simultaneamente. Se o softwa-
re fosse realmente instalado em um hominóide real, imagine o que isso
faria ao pobre hardware dele!
3. Não. Estritamente falando, o ato de deter identificadores de objetos des-
tinatários e mensagens interativas constituem idéias distintas: uma men-
sagem de difusão (broadcast) poderia ser enviada interativamente a todos
os objetos. Na verdade, em algum nível de implementação (mesmo em um
sistema concorrente), isso é provavelmente o que ocorre — a seqüência de
interação fica definida pelo sistema operacional. Da mesma forma, em
um meio assíncrono/concorrente, um objeto poderia enviar mensagens em
paralelo a todos os objetos cujos identificadores ele detenha; isso se fizes-
se sentido na aplicação segundo o desenho.
D
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
DIAGRAMAS DE ESTADO
iagramas de estado

6.Diagramas de Estado

O diagrama de estado para uma classe mostra os estados que os objetos


dessa classe podem assumir e as transições que eles podem fazer de es-
tado para estado.1 Acompanhando o trabalho de David Harel e outros (veja
Harel, 1987, ou Henderson-Sellers e Edwards, 1994, como exemplo), os auto-
res da UML estenderam a notação básica da transição de estado com, por
exemplo, estados aninhados. Neste capítulo, descrevo brevemente o diagrama
de estado básico, e em seguida exploro algumas elaborações proveitosas sobre
ele: a utilização de estados aninhados; a incorporação de argumentos de men-
sagens; a representação de estados concorrentes interagindo; e, finalmente, a
acomodação de atributos continuamente variáveis no diagrama de estado.2

6.1 Diagramas de Estado Básicos


Um diagrama de estado é ideal para a modelação de um atributo com as duas
características a seguir:

• o atributo possui poucos valores; e


• o atributo tem restrições em transições autorizadas entre esses valores.

1. Em casos extremamente raros, uma classe em si (contrariamente a seus objetos) apresenta


estados dignos de serem modelados com um diagrama de estado. Isso é similar à idéia de um
atributo de classe versus um atributo de instância, que discutimos no Capítulo 3.
2. O termo oficial na UML é diagrama de gráfico de estado, que é abreviado aqui como diagrama
de estado. Fica um pouco mais fácil para falar e eliminar a redundância construída no esque-
ma de palavras oficial. Note também que as tabelas de estado convencionais continuam úteis
— veja, por exemplo, Ward e Mellor, 1985, para mais detalhes.

165
166 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Por exemplo, consideremos a classe ItemComercializável, que contém os


atributos de instância preçoDeVenda: Dinheiro e condiçãoDeInspeçãoAtual: Con-
diçãoDeInspeção. Há duas diferenças qualitativas entre esses atributos:

1. O atributo preçoDeVenda, por se tratar de uma instância de Dinheiro, tem


um grande número de valores possíveis, tais como US$ 1,98, US$ 4,95,
US$ 5,95 e assim por diante. O atributo condiçãoDeInspeçãoAtual, por ou-
tro lado, tem somente um pequeno conjunto de valores possíveis, tais
como recebido (acabou de chegar do vendedor), sobInspeção (sendo exami-
nado minuciosamente, mesmo quando estamos falando), aprovado (pas-
sou pelo exame mais detalhado do inspetor) e rejeitado (descartado na
condição de sem valor).
2. Existe provavelmente pouca restrição empresarial aplicável nas altera-
ções permitidas a preçoDeVenda. Em outras palavras, o preçoDeVenda de
um objeto pode mudar de US$ 4,95 a US$ 5,95, ou de US$ 6,50 a US$ 5,99
ou o que for. Todavia, uma condiçãoDeInspeçãoAtual não pode mudar di-
retamente de recebido a aprovado sem passar primeiro pelo valor sobIns-
peção.

O atributo preçoDeVenda não é um candidato apropriado a ser modelado


por meio de um diagrama de estado porque ele tem muitos valores possíveis
e praticamente nenhuma restrição nas transições entre esses valores. O atri-
buto condiçãoDeInspeçãoAtual, por outro lado, tem as duas propriedades que o
tornam ideal para um diagrama de estado. Como já vimos, esse atributo tem
somente poucos valores possíveis e existem restrições significativas nas tran-
sições de um valor para outro. Um atributo de instância, tal como condição-
DeInspeçãoAtual, com as duas características ideais já listadas, e valores que
refletem os estados naturais de seu objeto proprietário, é denominado atributo
de estado. Um atributo de estado é um mecanismo para representar os estados
de um objeto.
A Figura 6.1 mostra o diagrama de estado para ItemComercializável.con-
diçãoDeInspeçãoAtual. Na figura, cada um dos retângulos contém um valor de
condiçãoDeInspeçãoAtual e representa o estado de um objeto ItemComercializá-
vel.3 As setas representam transições entre estados. Cada transição é anotada
com uma etiqueta de duas partes. O texto acima da linha descreve um evento,
que é normalmente provocado por uma mensagem de entrada para o objeto.

3. Conforme veremos no Capítulo 10, um retângulo em um diagrama de transição de estado de-


nota uma região no espaço-estado da classe. A região pode compreender um único ponto ou
um grupo de pontos.
Cap. 6 DIAGRAMAS DE ESTADO 167

Um evento causa uma transição de um estado para outro. A presença de vá-


rios eventos acima da linha indica que qualquer um desses eventos pode pro-
vocar a transição.4

Figura 6.1 Diagrama de estado para ItemComercializável.condiçãoDeInspeçãoAtual.

O texto abaixo da linha descreve a ação que é desencadeada por uma


transição entre estados. Esta ação é normalmente uma mensagem de saída
vinda do objeto. A presença de várias ações abaixo da linha indica que todas
as ações são executadas durante a transição. Para algumas transições, possi-
velmente não haverá ações associadas. Na Figura 6.1, mostro apenas um
exemplo de ação: uma mensagem de saída registrando o fato de que havia um
item rejeitado (a saber, self) dentro da remessa atual.

4. Estilisticamente, a UML padrão prefere utilizar um símbolo de corte inclinado para a direita
(/), em vez de uma linha horizontal, para separar eventos de ações. Portanto, nesta seção, você
talvez queira substituir as palavras “antes do (/)” por “acima da linha” e “após o (/)” por “abai-
xo da linha”.
168 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O estado inicial de um diagrama de estado, marcado por um ponto preto


situado no topo da Figura 6.1, é um estado no qual o objeto não despende
qualquer tempo. Em vez disso, o objeto imediatamente passa para o estado
inicialmente adquirido em sua geração ou inicialização. Essa transição desde
o estado inicial normalmente não é etiquetada; se bem que você, se assim o
desejar, pode etiquetá-la com o evento criador do objeto.
O estado final de um diagrama de estado, marcado por um “olho-de-boi”,
representa o término da atividade do objeto. Há um exemplo de um estado fi-
nal, na Figura 6.6. (Mais precisamente, como veremos quando examinarmos
os estados aninhados na próxima seção, o estado final representa o término
da atividade dentro do estado circundante).
O diagrama de estado da UML, que descrevi até o momento, associa
ações com transições, e é geralmente denominado de convenção de Mealy. En-
tretanto, a UML também suporta ações associadas com estados, nesse caso o
diagrama é denominado convenção de Moore. Eu mostro um exemplo de ações
em estados no decorrer deste capítulo. Portanto, na UML sempre há algo para
todos — quer se trabalhe em um ambiente Mealy, Moore, Mearly, Moorly ou
em qualquer outro local de trabalho.

6.2 Estados Aninhados


Nem todos os diagramas de estado são tão simples quanto o da Figura 6.1. Al-
guns diagramas de estado requerem estados internos, aninhados no interior
de outros estados externos. Como exemplo, vamos considerar os diagramas de
estado para a classe Máquina, que representa uma máquina de chão de fábrica
em uma aplicação de controle de fábrica.
A classe Máquina apresenta dois status: um status de operação atual, re-
presentado pelo atributo statusDeOperação, e um status de manutenção atual,
representado pelo atributo statusDeManutenção. Conforme exibido pela Figura 6.2,
statusDeOperação, o status de operação da máquina, tem quatro estados: esta-
doDeEspera, acelerando, funcionando e desacelerando.
No diagrama de estado, o evento

when (self.velocidadeReal >= velocidadeDeOperação)

é um evento de mudança na UML (conhecido em outras abordagens como


transição booleana ou transição predicativa). Supõe-se que ele ocorra no mo-
mento em que a expressão booleana entre parênteses (após a palavra-chave
when) se altera de false para true.
Igualmente, na Figura 6.2, a expressão:
Cap. 6 DIAGRAMAS DE ESTADO 169

[self.statusDeManutenção = Funcionando]

Figura 6.2 O diagrama de estado para Máquina.statusDeOperação.

é conhecida como condição de guarda. A condição de guarda em uma transição


significa que a transição se realiza somente se a expressão booleana entre col-
chetes for true quando o evento desencadeador ocorrer. Neste exemplo, a má-
quina só dará partida através do comando do operador se ela estiver apta a
entrar em funcionamento.
Isso leva-nos caprichosamente para a Figura 6.3, que mostra os três es-
tados de statusDeManutenção, o status de manutenção da máquina: Funcionan-
do, esperandoPorConserto e emConserto.
170 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 6.3 O diagrama de estado para Máquina.statusDeManutenção.

À primeira vista, Máquina provavelmente aparenta ter doze estados pos-


síveis — três statusDeManutenção multiplicado por quatro statusDeOperação.
Mas isso não corresponde à realidade se fizermos uma suposição razoável de
que, para esta específica aplicação, somente uma máquina que esteja apta a
entrar em funcionamento pode expressivamente acelerar, desacederar e assim
por diante. Dessa forma, existem na realidade apenas seis estados legítimos,
em razão de os dois atributos anteriormente, statusDeManutenção e statusDeO-
peração, não serem mutuamente independentes; statusDeOperação apenas tem
algum significado quando statusDeManutenção = Funcionando.
A Figura 6.4 mostra a combinação dos dois diagramas de estado constan-
tes nas Figuras 6.2 e 6.3, em que o segundo diagrama é aninhado dentro do
estado Funcionando do primeiro.5 Esse aninhamento dos quatro estados status-
DeOperação no interior do estado Funcionando ilustra a restrição segundo a
qual statusDeOperação somente tem significado quando statusDeManutenção =
Funcionando. De fato, esse aninhamento somente tem utilidade quando há res-
trições tais como as encontradas entre esses diagramas de estado para uma
classe.

5. Omiti as anotações nas transições apenas para fins de clareza neste exemplo.
Cap. 6 DIAGRAMAS DE ESTADO 171

Figura 6.4 O diagrama de estado, combinado ou aninhado, para


Máquina.statusDeManutenção e Máquina.statusDeOperação.

Observe que, agora, o estado aninhado representativo de operabilidade in-


dica que a máquina tem tanto o status de operação de operabilidade como o de
manutenção de Funcionando. Inversamente, já que o estado emConserto não
tem qualquer estado aninhado, o status de operação da máquina é indefinido
quando ela tem o status de manutenção de emConserto. Com a adição dessa
restrição a nosso modelo, podemos remover a condição de guarda [self.status-
DeManutenção = Funcionando] da transição estadoDeEspera /acelerando exibida
na Figura 6.2, porque a transição somente tem significado para uma máquina
que esteja apta a entrar em funcionamento.
Na Figura 6.4, existem dois símbolos do estado inicial — os pequenos
pontos pretos. O ponto externo indica que quando um objeto da classe Máquina
é criado, o valor de statusDeManutenção deve ser inicializado para Funcionan-
172 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

do. O ponto mais interno significa que, não importando quando um objeto da
classe Máquina entra no estado Funcionando — isto é, sempre que o valor do
statusDeManutenção ficar igual a Funcionando —, o valor do statusDeOperação
deverá ser inicializado para estadoDeEspera.
A Figura 6.5 mostra parte do diagrama de estado para uma máquina de
lavar roupa. Bastante semelhante à máquina de fábrica da Figura 6.4, a má-
quina de lavar roupa completa o ciclo dela somente quando estiver funcionan-
do, o que no caso desta máquina de lavar implica que sua tampa esteja
fechada. Entretanto, há uma diferença crucial entre a máquina de lavar roupa
e a máquina de fábrica típica: se a tampa da lavadora abrir, e em seguida fe-
char, ela não deverá retornar a seu estado inicial — parada — mas sim ao seu
estado um pouco anterior à abertura da tampa. Em outras palavras, a lava-
dora deve ser capaz de restabelecer o subestado prévio de funcionabilidade
quando o estado funcionabilidade for reiniciado.

Figura 6.5 Estado de MáquinaDeLavarRoupa com história.


Cap. 6 DIAGRAMAS DE ESTADO 173

Para representar isso, a UML segue David Harel quanto ao emprego do


símbolo de história (history symbol), um H presente no interior de um peque-
no círculo (veja Harel, 1987). Esse símbolo tem o seguinte significado: “Entrar
em qualquer subestado que estava ativo por último, mediante a entrada sub-
seqüente no estado circundante”. Se o símbolo de história caracteriza uma
transição para um subestado (como ele fez para parada na Figura 6.5), então
isso significa que “Entre neste subestado quando da primeira entrada no es-
tado circundante”. O símbolo de história pode dessa forma substituir o símbolo
do estado inicial por um subestado, conforme feito na Figura 6.5.

6.3 Estados Concorrentes e Sincronização


Esta seção aborda os estados concorrentes que um objeto pode ter e analisa
profundamente o tópico dos estados aninhados. Eu utilizo um exemplo corren-
te de um pedido comercial razoavelmente realista em uma companhia chama-
da Smibley & Futz, Inc. Muito embora a vida de um pedido na Smibley & Futz
seja bastante complexa, ela ainda constitui uma simplificação do que talvez
pudesse suportar um pedido em sua companhia. À medida que você for lendo,
tenha em mente que um pedido real talvez tenha diversos itens em um grande
número de estados, ou até mesmo itens parciais, quando somente uma parcela
da quantidade inicial puder ser remetida.
Vamos examinar inicialmente a Figura 6.6, que mostra os estados do sta-
tusDeAutorizaçãoDeCliente de um pedido (o atributo de estado cujos valores são
movidos principalmente pelo capricho do cliente, em vez de pelas decisões da
Smibley & Futz).
Quando um cliente liga para fazer um pedido, a companhia trata o pedido
como padrão (default), sob a forma de tentativa, estado no qual ele é submetido
à apreciação possivelmente para uma cotação de preço mas não pode ser ime-
diatamente efetivado. Assim que o cliente confirma o pedido, fato esse que
pode ocorrer durante a primeira ligação telefônica, o pedido assume o estado
confirmado. De acordo com a diretriz de trabalho da S&F, esse é o único esta-
do em que pode ocorrer um tipo de processamento real; retornaremos ao mes-
mo em breve.
174 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 6.6 Diagrama de estado para Pedido.statusDeAutorizaçãoDeCliente .

O cliente pode cancelar um pedido a menos que os artigos já tenham sido


remetidos, portanto, à condição de guarda, [self.statusDeCumprimento not= re-
metido]. Se o cliente cancelar o pedido, este fica — você adivinhou! — cance-
lado. Conforme mostrado na Figura 6.6, a ação de entrada para o estado
cancelado é a mensagem self.cancelar, acarretando que o objeto Pedido desfaça
qualquer alocação de estoque porventura feito para ele.6 Repare que os pedi-
dos “do tipo tentativa” são automaticamente cancelados após trinta dias, pro-
piciando um exemplo de uma cláusula after da UML: after (períodoDeTempo)
representa um evento temporal que ocorre quando o intervalo períodoDeTem-
po decorreu após a entrada no dado estado.
Agora é hora de olharmos novamente para o estado confirmado. A peque-
na linha dentro do estado, na Figura 6.6, é um tipo de apêndice (stub) da
UML, indicando um diagrama expandido no qual esse apêndice e outros de-
talhes incluídos do estado aparecem por inteiro. A Figura 6.7 é uma expansão
do estado confirmado.

6. A fim de se concentrar em diagramas de estado, este capítulo não se estende mais sobre as
ações mencionadas nos diagramas.
Cap. 6 DIAGRAMAS DE ESTADO 175

Figura 6.7 Diagrama de estado para Pedido.statusDeCumprimento,


uma expansão do estado confirmado.

O plano de trabalho na S&F é que pedidos grandes devem ser aprovados


tanto por um gerente de crédito como por um gerente de cliente. (Eu não de-
fino “grande” aqui, mas self.éGrande retorna true para um pedido grande.) Isso
confere para um pedido grande confirmado o subestado inicial esperandoApro-
vação, que discuto a seguir. Um pedido pequeno confirmado, que obtém apro-
vação automática, inicia no subestado aprovado.
Uma vez que um pedido é aprovado, ele está pronto para ser cumprido,
um estado em que os itens específicos dos tipos de produtos são alocados para
176 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

entrega ao cliente. Mas, primeiramente, o estoque deve ser verificado: quer o


estoque esteja disponível e a transição que está prestes a ser cumprida seja
ativada, quer o estoque se encontre exaurido e a transição para o estado atra-
soNoCumprimento seja ativada. Eu mostro isso no diagrama como duas tran-
sições com condições de guarda — por exemplo, [self.estoqueDisponível] — e
sem eventos.
Quando exibo essa abordagem para meus clientes e alunos, eles freqüen-
temente me perguntam: “Por que você não utilizou uma cláusula when, tal
como when (self.estoqueDisponível)?” Minha resposta é que aprovado é na rea-
lidade um estado transiente (ou estado de flow-through), significando que ele
não persiste. (Observe sua propriedade {transiente} na Figura 6.7.) Em outras
palavras, em vez de perambular em um estado transiente, um objeto faz uma
transição instantânea para outro estado, sem esperar por um evento. (Algu-
mas pessoas denominam uma transição dessas, desprovida de evento, como
transição não iniciadora.) Dessa forma, um estado transiente é como uma bi-
furcação em uma estrada, em que o objeto realiza uma transição baseado em
seja qual for a condição de guarda colocada como true.
O estado aprovado é transiente porque, nessa aplicação computadorizada,
não precisaríamos aguardar que o sobrinho de Smibley saísse novamente para
contar as caixas de WhizzoTM No-Cholesterol Dream Whip (Doce dos Sonhos
Isento de Colesterol WhizzoMR). Portanto, já que o sistema sabe ou não se há
estoque, e atua conformemente, não há evento de when para ser modelado.7
Note que o estado atrasoNoCumprimento é um estado antigo e comum não
transiente, com when (self.estoqueDisponível) provocando a transição para
cumprido.8 Quando o armazém expede os itens encomendados, o estado torna-
se remetido. Eu estava tentado a retirar remetido do superestado confirmado,
porque a S&f não cancelaria um pedido que tivesse sido remetido. Entretanto,
desde que isso deixaria o estado remetido “lá fora no frio”, ou teria requerido

7. Na realidade, um comando when(self.estoqueDisponível) a partir de aprovado seria proble-


mático: se o estoque já estivesse disponível quando da entrada em aprovado, o evento nunca
ocorreria. Há tempos propus a cláusula when ever (self.estoqueDisponível), que combina
when (self.estoqueDisponível) e a condição de guarda [self.estoqueDisponível] e permite
aos estados aprovado e atraso NoCumprimento serem combinados. Entretanto, essa cláusu-
la não “pegou”. Em vez disso, alguns modeladores, inclusive os autores de outros trabalhos
sobre a UML, estão se valendo de estratagemas para tratar dessa questão utilizando when
para significar whenever. Outros modeladores são mais absolutos em suas rejeições contra
estados transientes, mas, devido ao problema acima, considero os estados transientes muito
apropriados. Na próxima seção, revelo outra forma de representá-los, o que talvez me coloque
em dificuldades perante os ministros da Ordem de Modelagem do Novo Mundo.
8. Por conveniência, neste modelo assumo que o objeto Pedido, em seu próprio termo, tem um
atributo estoqueDisponível retornando um valor Booleano após verificação da disponibilida-
de de inventário.
Cap. 6 DIAGRAMAS DE ESTADO 177

outro aninhamento, decidi modelar esse requisito com a condição de guarda


[self.statusDeCumprimento not= remetido], conforme mostrado na Figura 6.6.
O apêndice esperandoAprovação da Figura 6.7 indica uma expansão, que
é mostrada na Figura 6.8. Esse diagrama de estado modela uma espera pur-
gatorial (purificadora) de um grande pedido para o benefício dos gerentes de
crédito e gerentes de cliente. Visto que esses gerentes são seres independen-
tes, dividimos a expansão de esperandoAprovação em dois diagramas concor-
rentes — utilizando uma linha pontilhada vertical.

Figura 6.8 Diagrama de estado para Pedido.statusDeAprovação,


uma expansão do estado esperandoAprovação.

Mas ainda não estamos prontos! A aprovação na S&F significa esperar


que ambos os gerentes façam seus gestos de aprovação; a rejeição por parte
de qualquer um deles arremessa qualquer pedido para sua completa destrui-
ção. Para controlar essa situação, exploro a barra de sincronização (synchro-
178 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

nization bar) da UML, mostrada na parte inferior de esperandoAprovação na


Figura 6.8. Essa barra grande e escura indica que ambas as aprovações, apro-
vadoPeloGerenteDeCrédito e aprovadoPeloGerenteDeCliente, devem ser atingi-
das antes de prosseguirmos. Quando ambos os estados são inicializados, nós
imediatamente nos movemos para aprovado.
Uma vez que a transição para rejeitado pode ocorrer desde esperandoA-
provaçãoDoGerenteDeCrédito ou esperandoAprovaçãoDoGerenteDeCliente, não
precisamos de qualquer sintonização — somente um par de transições diretas
para rejeitado. Incidentalmente, a mensagem gerenteDeCliente.notificarRejeita-
do (self,...) trata-se apenas de uma mensagem de cortesia dizendo ao outro ge-
rente para não se preocupar com a checagem de um pedido anteriormente
rejeitado.

6.4 Estados Transientes de Argumentos de Mensagens


de Saída
Conforme discutimos na seção 6.2 sobre estados aninhados, a condição de
guarda booleana e as expressões when são construídas a partir de uma com-
binação destes elementos:

• atributos de um objeto (ou, às vezes, variáveis particulares);


• argumentos de mensagens de entrada de dados ao objeto;
• argumentos retornados de mensagens de saída para outros objetos;

Vimos exemplos dos dois primeiros dos elementos na Figura 6.2. Por
exemplo, a condição de guarda
[self.statusDeManutenção = Funcionando]

contém um atributo, statusDeManutenção. A expressão when

when (self.velocidadeReal = velocidadeDeOperação)

tem outro atributo, velocidadeReal, e um argumento de mensagem de entrada,


velocidadeDeOperação, que se origina de operadorIniciaAMáquina (velocidade-
DeOperação).
Até o momento, entretanto, não vimos o impacto da terceira espécie de
argumento, um argumento de retorno desde uma mensagem de saída. Um
exemplo do modelo de máquina de chão de fábrica, na Figura 6.2, foi velocida-
deDeOperaçãoOK, na mensagem de saída para motorPrincipal:
Cap. 6 DIAGRAMAS DE ESTADO 179

MotorPrincipal.ligar (velocidadeDeOperação, out


velocidadeDeOperaçãoOK)

A mensagem ligar informa ao objeto motorPrincipal para girar o motor real


de fábrica à velocidadeDeOperação. Entretanto, se o valor de velocidadeDeOpe-
ração exceder ao que é permitido para a marca de motor instalada, então o mo-
torPrincipal irá roncar e nada fará, exceto retornar para velocidadeDe-
OperaçãoOK como false. Mas, então, temos um problema de modelagem. Se ve-
locidadeDeOperaçãoOK retorna como false, não podemos simplesmente fazer
uma cômoda transição para acelerando, porque a máquina não está acelerando
— ela ainda está parada!
Teoricamente, aqui colocaríamos um par de condições de guarda — um
para cada valor de velocidadeDeOperaçãoOK (true ou false) — como faríamos se
motorPrincipal.ligar fosse uma mensagem de entrada em vez de uma mensa-
gem de saída. Mas é tarde demais para uma condição de guarda de acordo com
uma ação motorPrincipal.ligar em razão de que a condição de guarda está tes-
tando um argumento de retorno (velocidadeDeOperaçãoOK) da mensagem mo-
torPrincipal.ligar em si. Para lidar com esse tipo de situação, utilizo um estado
transiente, acionandoMotor, conforme mostrado na Figura 6.9, uma cópia re-
vista da Figura 6.2.
Este estado transiente no meio da Figura 6.9 é um veículo para as duas
condições de guarda [velocidadeDeOperaçãoOK] e [not velocidadeDeOpera-
çãoOK]. Em outras palavras, ele faz a transição de estadoDeEspera para acele-
rando, uma transição que é condicional no tocante ao valor de velocidadeDe-
OperaçãoOK.
Finalmente, a transição de retorno ao estado de EstadoDeEspera provoca
a emissão de duas mensagens de saída (para desligar o motor principal e acio-
nar o freio de emergência novamente).

6.5 Atributos Continuamente Variáveis


Conceitualmente, alguns atributos de coisas do mundo real variam de forma
contínua, em vez de discretamente. Em outras palavras, se nós os medíssemos
com a máxima precisão encontraríamos um número infinito de valores, não
apenas alguns valores discretos. Eu os nomeio de atributos continuamente va-
riáveis. 9

9. Eu me baseio no termo fluxo de dados contínuo, utilizado em Ward e Mellor, 198, para criar
o termo atributo continuamente variável. A idéia é que um atributo desse tipo — de qualquer
forma, a princípio — varia continuamente em vez de só variar depois de eventos discretos. A
pressão do ar em sua sala constitui um exemplo de um atributo continuamente variável.
180 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 6.9 Parte do diagrama de estado para Máquina.statusDeOperação.

Por exemplo, uma reação desenvolvendo-se em uma câmara de reação


em uma fábrica de produtos químicos tem uma temperatura em curso, a
qual é continuamente variável. Conforme mostrado pela Figura 6.10, o atributo
Reação.temperaturaDeOperação representa a temperatura da reação (onde
Reação é a classe de reações químicas em uma câmara). Muito embora tempe-
raturaDeOperação constitua um atributo contínuo, em vez de um atributo dis-
creto, ela também pode desempenhar certo papel em um diagrama de estado.
Observe que cada um dos três estados do diagrama de estado — frioDe-
mais, quenteDemais e OK — corresponde às seguintes faixas de valores da tem-
peraturaDeOperação:10

>tempMáx // quente demais

10. Como veremos no Capítulo 10, qualquer assertiva desse tipo é na realidade equivalente à de-
finição de uma região de espaço-estado da classe.
Cap. 6 DIAGRAMAS DE ESTADO 181

≤tempMáx e ≥ tempMín // OK
<tempMín // frio demais

Note, da mesma forma, que a maioria das transições no diagrama são de-
sencadeadas por uma assertiva when (à parte da assertiva inicial). Isso é algo
próprio das transições entre estados caracterizadas por serem simplesmente
condições de um atributo. Se você achar essa abordagem de modelagem exces-
sivamente repleta de assertivas when, considere a alternativa constante no
exercício 6, deste capítulo.
O diagrama de estado na Figura 6.10 ilustra a convenção de Moore no
qual se colocam mensagens de saída em estados, em vez de em transições (a
qual corresponde à convenção de Mealy).11 Por exemplo, sempre que o estado
frioDemais for atingido, a mensagem aquecedor.ligar será enviada. A palavra-
chave entry no estado frioDemais indica que a mensagem é enviada quando da
entrada nesse estado. (Outras palavras-chave possíveis na UML são: exit,
para a ação realizada ao deixar-se um estado; e do, para a atividade executada
enquanto se está em um estado. Repare que entry e exit denotam ações atômi-
cas, enquanto do denota uma atividade em curso.)

6.6 Resumo
O diagrama de estado mostra os estados admissíveis que os objetos de uma
dada classe podem assumir e as transições permitidas entre pares de estados.
O diagrama é útil para a modelagem de classes cujos objetos têm um atributo
de estado com estas duas propriedades: o atributo pode assumir um pequeno
número de valores possíveis, e as transições permitidas entre esses valores
são restritas. Um atributo que exiba essas propriedades, com valores que re-
flitam os estados naturais de seu objeto proprietário, é chamado atributo de
estado.
Cada estado simples, não aninhado no diagrama de estado, corresponde
a um valor possível de um atributo de estado ou, às vezes, a uma faixa de va-
lores desse atributo. A transição inicial de um diagrama de estado, que conduz
um objeto para seu estado inicial, é indicada por um ponto preto na seta. A
transição final apresenta um “olho-de-boi” na cabeça da seta.

11. Observe que a Figura 6.10 utiliza tanto a convenção de Mealy como a de Moore no mesmo
diagrama de estado, o que é perfeitamente aceitável na UML.
182 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 6.10 Diagrama de estado para Reação.


Cap. 6 DIAGRAMAS DE ESTADO 183

Cada transição entre um par de estados aparece como uma seta no dia-
grama de estado. O desencadeador de uma transição entre estados é um even-
to, que fica anotado acima de uma linha horizontal ou antes de um símbolo
de corte inclinado para a direita (/), em algumas ferramentas. A presença de
mais de um evento acima da linha indica que qualquer um dos eventos lista-
dos pode desencadear a transição. Uma condição de guarda na forma de uma
[expressãoBooleana] talvez apareça após um evento, o que significa que se a ex-
pressãoBooleana for true, a transição poderá se realizar assim que ocorrer o
evento desencadeador.
Existem três tipos de evento na UML: evento de chamada (call event),
evento de mudança (change event) e evento de tempo decorrido (elapsed-time
event). O evento de chamada ocorre mediante o recebimento de uma mensa-
gem de entrada a partir de outro objeto. (Uma variação do evento de chamada
é o evento de sinal [signal event], que ocorre mediante o recebimento de um
sinal explícito proveniente de outro objeto.) O evento de mudança, indicado
por uma cláusula when (expressãoBooleana), ocorre quando da transição de
uma expressãoBooleana de false para true. O evento de tempo decorrido, indi-
cado por uma cláusula after (períodoDeTempo), ocorre no término de um perío-
doDeTempo, o qual é normalmente medido a partir do tempo de entrada até
um dado estado.
Uma ação é normalmente definida como uma mensagem de entrada para
outro objeto ou para self. As ações que acompanham uma transição entre es-
tados (conhecidas como a convenção de Mealy) aparecem abaixo de uma linha
horizontal ou, em algumas ferramentas, depois de um símbolo de corte incli-
nado para a direita (/). A presença de mais de uma ação abaixo da linha indica
a ocorrência de todas as ações listadas.
As ações que se efetivam nos estados (conhecidas como a convenção de
Moore) são listadas dentro do próprio símbolo de estado, em seguida a uma
destas palavras-chave: entry (para ações realizadas quando da entrada no es-
tado), exit (para ações realizadas quando da saída do estado) e do (para ações
realizadas durante a residência no interior do estado).
Uma classe talvez tenha dois ou mais atributos de estado que podem ser
proveitosamente modelados com diagramas de estado. A utilização de diagra-
mas de estado dessa maneira geralmente leva a ter mais de um diagrama de
estado para a classe, ou ter diagramas de estado aninhados, no caso de os atri-
butos não serem totalmente independentes entre si. Um diagrama de estado
aninhado revela estados internos dentro de estados externos. Para evitar que
aconteça um amontoamento gráfico, a UML permite ao modelador exibir es-
tados aninhados como apêndices (stubs), os quais são expandidos em detalhe
184 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

e na íntegra em um diagrama de estado separado. A UML direciona o retorno


para um estado aninhado previamente desativado — o último subestado ativo
do estado circundante — com um H estilizado no interior de um pequeno cír-
culo (significando “história”). Um ponto preto sobre um estado interno desig-
na-o como o primeiro estado interno a tornar-se ativo quando o estado
circundante externo entrar em ação, a menos que esteja presente um símbolo H.
Mesmo quando os atributos de estado aparecerem em diagramas de es-
tado separados (ou seja, quando eles não estiverem aninhados uns dentro dos
outros), eles ainda poderão ter alguma espécie de interdependência. Por exem-
plo, a transição de um atributo talvez apenas ocorra se um outro atributo es-
tiver com um dado valor. (Essa limitação pode ser modelada com uma
assertiva de condição de guarda.) De maneira similar, a transição de um ob-
jeto para um estado pode ser permitida somente depois que ambos os atribu-
tos chegarem a valores apropriados, independentemente de qual chegou
primeiro. Se bem que uma condição de guarda possa novamente ser aqui apro-
veitada no tocante à modelagem, a UML oferece a barra de sincronização para
marcar graficamente o ponto de encontro entre os dois atributos de estado
concorrentes.
A orientação a objeto complica o diagrama de transição de estado conven-
cional, pelo fato de uma mensagem de saída enviada durante as transições
“sob condições de guarda” de um estado poder ter argumentos de retorno que
aparecem nas expressões booleanas das condições de guarda das transições.
Essa situação exige o acréscimo de um estado transiente para o diagrama de
estado, como um ponto de ramificação para a transição que provoca o envio da
mensagem de saída. As transições originais “sob condições de guarda” conse-
qüentemente se tornariam transições originárias do estado transiente adicional.
Algumas classes — especialmente aquelas em sistemas de controle de
processos — possuem atributos que são conceitualmente contínuos, em vez de
discretos, quanto aos valores que podem assumir. Alguns desses atributos con-
tinuamente variáveis podem proporcionar diagramas de estado de valor. Es-
tados baseados em um atributo continuamente variável são tipicamente
fundados em faixas de valores do atributo. Uma marca característica de tal
diagrama de estado é a presença de eventos, com muitas mudanças, desenca-
deadores de transições.

6.7 Exercícios
1. Suponha que um desenhista tenha criado um diagrama de estado com 57
estados e 93 transições, muitos deles sob condições de guarda. A opinião
geral dos críticos é que o diagrama é irremediavelmente emaranhado e
Cap. 6 DIAGRAMAS DE ESTADO 185

incompreensível. O que você recomendaria a esse desenhista? (Emigrar


para a Ilha Baffin não é uma resposta aceitável.)
2. E no caso de um desenhista que, depois de ter passado um dia inteiro fi-
tando uma classe, estava prestes a saltar do 38o andar de um prédio por-
que não conseguia propor um diagrama de estado digno dessa classe?
Qual seria o seu conselho para esse desenhista?
3. O diagrama de estado na Figura 6.4 tem dois símbolos de estado inicial,
os grandes pontos pretos: um conduz ao estado funcionando e o outro ao
subestado estadoDeEspera. Será que um desses símbolos poderia ser su-
primido sem que houvesse qualquer alteração no significado do diagra-
ma? Se a resposta for positiva, qual deles? Consiste uma boa prática
eliminar símbolos do estado inicial dessa maneira?
4. Existem alguns problemas com o diagrama de estado na Figura 6.10. Por
exemplo, a válvula 1 e a válvula 2, que presumivelmente liberam produ-
tos químicos reativos para a câmara de reação, nunca ficam fechadas. (A
válvula 3, uma válvula de escape que tem a função de drenar a câmara
de reação, abre e fecha.) A cláusula when final, when (reação.temperatu-
raDeOperação <= temperaturaFinal) também apresenta um problema: se
reação.temperaturaDeOperação já for menor que temperaturaFinal quando
da entrada no estado reaçãoTerminada, a cláusula when nunca será exe-
cutada. Como você solucionaria esses problemas e quaisquer outros que
você tenha encontrado na Figura 6.10?
5. O exemplo na seção 6.4 indicou que os atributos continuamente variáveis
devem ser apenas encontrados no mundo do controle de processos em
tempo real. Isso está correto? Em que sentido Ações.preço poderia ser um
atributo continuamente variável? Ele poderia ter um diagrama de estado
significativo?
6. De que forma você poderia utilizar uma tabela, em vez de um diagrama de
estado, para modelar as transições entre faixas de valores de um atributo
continuamente variável, como o de temperaturaDeOperação na Figura 6.10?
7. Considere uma aplicação na Larnham Goode, Inc., uma pequena compa-
nhia provedora de seminários. Por simplicidade, suponha que a compa-
nhia ofereça só um seminário e que apenas tenha um instrutor (cansado
e em frangalhos). Cada semana do calendário de ensino representa uma
instância da classe SemanaDeSeminário. O status de reserva para uma
dada semana é mantido em SemanaDeSeminário.statusDeReserva, que pode
conter os valores disponível, reservadoExperimentalmente e reservadoSegu-
186 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

ramente. Quando um novo objeto SemanaDeSeminário é criado (ao ser pos-


to no calendário), é dado ao mesmo o statusDeReserva inicial de disponível.
Qualquer cliente pode fazer uma reserva para uma dada semana, com
ou sem depósito. O depósito concede ao cliente uma condição de reserva
segura; na ausência de depósito o cliente apenas fica com uma reserva
por tentativa. Mostre o diagrama de estado para os três estados do sta-
tusDeReserva que acabei de descrever.
8. Um belo dia, o sr. Larnham Goode, estimado presidente da companhia de
seminários, conclui que algumas semanas são mais solicitadas do que ou-
tras. Assim, a companhia estabelece o conceito de semanas de pico (alta-
mente populares), em oposição às semanas normais. A constante éPopular
capta a popularidade de uma SemanaDeSeminário. (Em outras palavras,
para uma semana popular, atribui-se a éPopular o valor fixo true quando
o objeto semana popular é gerado.) Em semanas de pico somente são per-
mitidas reservas seguras; para garantir esse tipo de reserva, o cliente
terá de deixar um grande depósito. “Grande” é definido como “pelo menos
a quantia representada por depósitoDePico”, e podemos, por hipótese, di-
zer que depósitoDePico > 0. Como você modificaria seu diagrama de esta-
do do exercício 7 para refletir essa nova diretriz da empresa?

6.8 Respostas
1. Certifique-se de que os seus diagramas de estado são abstrações de com-
portamento de classe. Um diagrama de estado não capta — e não deve
captar — todas as facetas e algoritmos possíveis da classe. Se você acha
que seu diagrama de estado está se tornando uma “miscelânea” de esta-
dos e condições, então muito provavelmente você gostaria de repensar
sua noção da classe — ou, no mínimo, posteriormente, abstrair seu dia-
grama. Você também tirará proveito se dividir o seu diagrama único e
emaranhado em vários diagramas de estado, cada um deles baseado em
um particular atributo de estado.
Se o seu modelo de estado de uma classe é terrivelmente complicado,
então você deveria considerar a decomposição desta em subclasses, com
cada subclasse assumindo o comportamento do estado mais importante
da classe original. Por exemplo, você poderia introduzir duas ou três sub-
classes de Máquina, tais como MáquinaFuncionando, MáquinaEsperandoPor-
Conserto e MáquinaEmConserto. (Essa idéia de formação de subclasses
baseadas em estados é muito fortemente enfatizada no trabalho de
Shlaer e Mellor, 1992.)
Cap. 6 DIAGRAMAS DE ESTADO 187

2. Não fique preocupado se a classe na qual você está trabalhando não re-
velar algo profundo por meio de um diagrama de estado. Muitas classes
não produzem diagramas de estado dignos de menção, porque seus atri-
butos podem assumir centenas de valores ou suas transições são irrestri-
tas. Capte o comportamento dessas classes por meio de invariantes de
classe e definições de operações e atributos de classe, e não se aborreça
com os diagramas de estado para essas classes. (Eu abordarei essas defi-
nições no Capítulo 10.)
3. Sim, se você suprimir o símbolo de estado inicial que leva à Funcionando,
o símbolo de estado inicial remanescente vai sugerir que um novo objeto
Máquina inicie no estado/subestado Funcionando/estadoDeEspera. Entre-
tanto, eu não gosto de fazer isso. Minha razão: o que aconteceria se mais
tarde tivéssemos um conjunto de subestados dentro de um outro estado,
com seu próprio símbolo de estado inicial? Dessa forma, nossa supressão
do ponto preto externo faria com que o diagrama se tornasse ambíguo.
4. Para as válvulas perpetuamente abertas, você presumivelmente precisa-
rá acrescentar um estado tal como encherCâmaraDeReação para lidar com
o primeiro problema. Se o seu local de trabalho utiliza when para signi-
ficar whenever (sempre), em conseqüência você não tem o segundo proble-
ma. Se when simplesmente significa when (quando) (como em, “no
momento exato em que false muda para true”), então você poderia intro-
duzir um estado transiente antes de reaçãoTerminada para verificar se
reação.temperaturaDeOperação <= temperaturaFinal.
5. Muito embora atributos continuamente variáveis se manifestem mais ob-
viamente em sistemas de controle de processos em tempo real, também
se pode encontrá-los em alguns sistemas comerciais. Da mesma forma
que o atributo Reação.temperaturaDeOperação da seção 6.4 teoricamente
provaria ser contínuo se pudéssemos medi-lo com freqüência e precisão
suficientes, o atributo Ações.preço poderia ser considerado continuamente
variável. Muito embora nós não possamos medi-lo tão freqüentemente
quanto Reação.temperaturaDeOperação, nossa aplicação poderia receber
uma torrente de preços de ações (semelhante às temperaturas de um rea-
tor), sem pistas sobre quaisquer “eventos contra a corrente” que, em ul-
tima análise, provocariam mudanças no preço.
Portanto, Ações.preço poderia ter um diagrama de estado muito simi-
lar àquele de Reação.temperaturaDeOperação, com os nomes de estado mo-
dificados para razoavelmenteCotadas, superCotadas e subCotadas, para
preservar a aplicação. Uma transição para superCotadas, por exemplo, po-
188 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

deria provocar uma mensagem de saída para a venda da posição naquela


ação. A mensagem talvez fosse algo como:

contaDeAções.venderPosição (self)

6. A abordagem mais simples seria colocar as faixas de valores do atributo con-


tinuamente variável tanto nas linhas como nas colunas da tabela. Assim, as
ações para cada transição apareceriam como mensagens de saída no retân-
gulo apropriado da tabela. O exemplo da reação química, constante na Fi-
gura 6.10, poderia aparecer conforme mostrado na tabela abaixo:

Para frioDemais OK quenteDemais


De

frioDemais entry/ — alarmeDaOper. //impossível


aquecedor.ligar tonal.DeSomOK
OK entry/ alarmeDaOper. — alarmeDaOper.
aquecedor.desligar tonal.DeSomFrio tonal.DeSom
refrigerador.desligar Demais QuenteDemais
quenteDemais entry/ //impossível alarmeDaOper. —
resfriador.ligar tonal.DeSomOK

A primeira coluna indica o estado original antes da transição. A segunda


coluna indica as ações de Moore — as ações associadas a um estado es-
pecífico, preferentemente a uma particular transição. A terceira, quarta
e quinta colunas, mostram as ações de Mealy — as ações que ocorrem du-
rante uma transição em particular.
7. Os três valores de statusDeReserva são mostrados como estados na Figura 6.11:
Passagem de entrada SemanaDeReservasDeClientes provoca uma transi-
ção, quer para o estado reservadoExperimentalmente, quer para o estado
reservadoSeguramente. As condições de guarda mutuamente exclusivas,
[quantiaDepositada = 0] e [quantiaDepositada > 0], distinguem entre as
duas possíveis transições. Incidentalmente, uma quantiaDepositada nega-
tiva, se tecnicamente possível, evitaria qualquer transição desde o estado
disponível.
Cap. 6 DIAGRAMAS DE ESTADO 189

Figura 6.11 Parte do diagrama de estado para SemanaDeSeminário.statusDeReserva.

8. A Figura 6.12 mostra essa nova mudança a respeito de reservas. Para re-
fletir essa nova complexidade na diretriz da companhia, a condição de
guarda em cada uma das transições agora tornou-se mais complexa. Eu
tive alguns problemas ao tentar escrever as duas condições de guarda de
forma compreensível, portanto experimentei com as diversas formas des-
sas longas expressões booleanas. Tentei not self.éPopular, por exemplo,
mas descobri que self.éPopular = false é mais fácil de ser entendida. Você
talvez queira experimentar mais adiante para derivar melhores formas
para as expressões booleanas, talvez utilizando-se desta tabela lógica
como algo proveitoso; avaliando o and dos valores das linhas e colunas.

AND (E): éPopular = false éPopular = true

quantiaDepositada = 0 reservadaExperimentalmente reservadaExperim.


0 < quantiaDepositada < quantia reservadaSeguramente reservadaExperim.
DepositadaNoPico
qua ntia Depositada >= reservadaSeguram. quantiaDepositadaNoPico
reservadaSeguramente
190 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 6.12 Parte do diagrama de estado para SemanaDeSeminário.statusDeReserva.


A rquitetura e diagramas
de interface
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
ARQUITETURA E DIAGRAMAS DE INTERFACE
7.Arquitetura e Diagramas de Interface

D o Capítulo 3 ao 6 foram abordados os conceitos que serão utilizados dia-


riamente no desenho orientado a objeto, o coração e a alma da UML. Nes-
te capítulo, a primeira seção introduz a UML para a modelagem de
arquitetura de sistemas, o que inclui o seguinte:
• arquitetura de software, tais como pacotes (tratada na seção 7.1.1)
• arquitetura de hardware, tais como processadores múltiplos (vista na
seção 7.1.2)
• a interação entre a arquitetura de software e a arquitetura de hard-
ware, tal como a especificação de construções de software para arte-
fatos de hardware (abordada na seção 7.1.3)

A segunda seção deste capítulo mostra a notação para modelagem de ja-


nelas na interface humana e as rotas de navegação de janelas. Por haver mui-
to pouco material na UML atual, assistindo diretamente no tocante à
modelagem da interface humana, criei alguns estereótipos da UML para esta
finalidade.

7.1 Representação da Arquitetura de Sistemas


A modelagem de arquitetura (architecture modeling) se inicia com um modelo
essencial (um que não esteja comprometido com qualquer tecnologia em par-
ticular) e faz o mapeamento dele para uma tecnologia escolhida. Você pode
utilizar a UML para exibir tanto as interligações entre artefatos tecnológicos
quanto a distribuição de seu sistema de software por meio desses artefatos.

191
192 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Nesta seção, analisamos pacotes da UML, que revelam somente particiona-


mento de software, diagramas de implantação da UML (que revelam somente
particionamento de hardware) e diagramas de implantação da UML que re-
velam os dois tipos de particionamento — de hardware e de software.

7.1.1 Pacotes
Uma vez que nem sempre dois locais de trabalhos diferentes tem a mesma no-
ção do que é um pacote, eu posso, normalmente, melhor descrevê-lo como um
agrupamento de elementos de software. Dentro de um sistema orientado a ob-
jeto, um pacote é geralmente uma coleção de classes. Ele poderá ser uma co-
leção de classes em uma biblioteca adquirida, uma coleção de classes para
uma particular aplicação ou uma coleção de classes que capture aspectos de
algo do mundo real (FinançasDeCliente, RemessasDeCliente e PerfilDeCliente;
cada uma delas se ocupando de um conjunto de características relativas a
cliente).
A UML ilustra um pacote como uma pasta estilizada, similar ao ícone de
pasta utilizado em muitas aplicações de desktop, conforme exemplificado na
Figura 7.1:

Figura 7.1 Pacote simples da UML mostrando dois pacotes interdependentes.

Os dois pacotes da Figura 7.1 presumivelmente residem na biblioteca de


um local de trabalho que se utiliza de Sistemas de Informações existentes em
um hospital. O PacoteDePaciente é uma coleção de classes referentes a um pa-
ciente, tais como Paciente e HistóriaMédicaDePaciente. O PacoteDeAlaHospitalar
é uma coleção de classes referentes a uma ala, tais como Ala, Cama e Enfermei-
ra.1 O nome plenamente qualificado para esta classe Cama é PacoteDeAlaHos-
pitalar::Cama. A utilização do nome plenamente qualificado distingue essa

1. Em algumas metodologias, pacotes tais como PacoteDePaciente e PacoteDeAlaHospitalar são


denominados subdomínios do domínio de negócio. Eu retorno ao assunto dos domínios no Ca-
pítulo 9.
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 193

classe de uma classe Cama em qualquer outro pacote, tal como em Inventário-
DeHospital.
Uma seta pontilhada entre dois pacotes significa dependência. Neste
exemplo, a cabeça da seta nas duas extremidades do corpo da seta revela uma
dependência em ambos os sentidos. A seta apontada para a direita indica que
PacoteDePaciente depende de PacoteDeAlaHospitalar (talvez Paciente esteja se
referindo à ocupação de Cama), e a seta apontada para a esquerda indica que
PacoteDeAlaHospitalar depende de PacoteDePaciente (talvez Enfermeira conte-
nha alguma referência a Paciente).
O diagrama de pacote da UML, na Figura 7.2, ilustra como os pacotes po-
dem estar contidos dentro de pacotes, da mesma forma que as pastas podem
estar contidas no interior de pastas em aplicações relativas a gerenciamento
de arquivos.2

Figura 7.2 Diagrama de Pacote da UML para uma aplicação.

O pacote DomínioDeNegócio é simplesmente um agrupamento dos dois


pacotes vistos anteriormente. O pacote DomínioDeArquitetura é um agrupa-
mento de pacotes adquiridos da biblioteca para a plataforma hardware/soft-

2. N.T.: DB significa “Banco de Dados”.


194 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

ware na qual a aplicação deverá ser executada: BibliotecaDeSuporteDeGUI (que


contém as classes para implementar as características da interface gráfica do
usuário) e BibliotecaDeSuporteDeDB (que contém classes adicionais provenien-
tes de terceiros para suporte baseado em banco de dados). A elipse vertical in-
dica que há outras bibliotecas no pacote do DomínioDeArquitetura.
O pacote DomínioDeAplicação contém dois pacotes que são específicos a
uma única aplicação, para admissão e alta de pacientes.
O pacote AplicaçãoParaAdmissão/AltaDePaciente contém o software que im-
plementa as diretrizes e procedimentos quanto à admissão e alta de um pa-
ciente. Uma vez que as classes nesse pacote fazem muitas referências a
classes em PacoteDePaciente e PacoteDeAlaHospitalar, coloquei setas de depen-
dência de AplicaçãoParaAdmissão/AltaDePaciente a PacoteDePaciente e a Pacote-
DeAlaHospitalar. De forma alternativa, eu poderia ter mostrado esse fato com
uma única seta dirigida ao pacote integral DomínioDeNegócio.
O pacote GUIParaAdmissão/AltaDePaciente contém o software para a inter-
face da aplicação. As classes dentro do pacote GUIParaAdmissão/AltaDePaciente
são construídas utilizando-se de artefatos provenientes da BibliotecaDeSupor-
teDeGUI — daí a seta de dependência do primeiro a esse último.

7.1.2 Diagramas de implantação para artefatos de


hardware
A Figura 7.3 ilustra a tecnologia de hardware de um sistema cliente/servidor
em três camadas.3 Nas mesas dos usuários, as estações de trabalho (worksta-
tions) estão conectadas a um servidor de arquivo de departamento via uma
rede de área local (local-area network — LAN). O servidor de arquivo depar-
tamental pode reter objetos individuais (especificamente suas variáveis de
instância) e/ou o código executável para classes de objetos. Os servidores de
arquivo departamentais, por sua vez, estão conectados ao servidor da compa-
nhia via uma rede em uma área mais extensa (wide-area network - WAN). O
servidor da companhia pode conter informação central à corporação ou que
precisa ser mantida a salvo. Cada departamento tem uma workstation opera-
dora, diretamente conectada ao servidor de arquivo via LAN e ao servidor da
companhia via WAN.
A Figura 7.4 mostra uma abstração da Figura 7.3, denominada de dia-
grama de implantação da UML, que retrata a localização das unidades de tec-
nologia e os vínculos (links) de comunicação entre eles.4

3. Utilizo o termo camada para indicar o nível de hardware.


4. Isso é aproximadamente equivalente ao diagrama de interligação de arquitetura de Hatley e
Pirbhai, 1988, e Hatley e outros, 2000.
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 195

Figura 7.3 Esquema ilustrativo da tecnologia em um sistema


cliente/servidor em três camadas.

Figura 7.4 Diagrama de implantação para sistema de negócio


cliente/servidor em três camadas.
196 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O exemplo na Figura 7.4 mostra que diversas workstations de usuários


estão conectadas a um único servidor de arquivo departamental via um link
de comunicação local (denominado interDeptLink, da classe LAN). Cada servidor
de arquivo departamental está conectado a uma workstation via um link (de-
nominado opLink, também da classe LAN). Os servidores de arquivo departa-
mentais, e as workstations operadoras, estão conectados ao servidor da
companhia via certos links (denominados, respectivamente, deptCorpLink e op-
CorpLink, ambos da classe TCPIP).
Um diagrama de implantação, tal como o mostrado na Figura 7.4, corres-
ponde a uma estrutura para a qual você pode anexar todos os tipos de esta-
tística e números modelos para especificar as unidades de tecnologia e seus
vínculos. Por exemplo, você poderia especificar certa workstation como perten-
cente à classe Blatz-888-100 (uma maravilhosa workstation da Ignatius Blatz
Enterprises), com tantos gigabytes de RAM e tantos terabytes de disco rígido.
Ou você poderia definir interDeptLink como da classe MellactoLAN (a LAN mais
vendida da Sid Melly Networks), com certo tipo de protocolo e largura de banda.
Você também poderá indicar a multiplicidade dos nós conectados,
como tenho na Figura 7.4. Por exemplo, visualizamos do diagrama que
cada estaçãoDeUsuário é conectada a precisamente um servidorDeDeparta-
mento, e que um dado servidorDeDepartamento tem conexão com, no mínimo,
uma estaçãoDeUsuário.

7.1.3 Diagramas de implantação para construções de


software
Na Figura 7.5, há três processadores: uma máquina de guiamento (um Blatz
Super5000), um controller mestre de superfície de controle (um WiggleZap 2B)
e um controller de back-up de superfície de controle (também um WiggleZap
2B). A máquina de guiamento se comunica com cada controller de superfície
de controle via um comando (bus) de guiamento.
Bem, isso é hardware puro — assim como o diagrama de implantação na
Figura 7.4. Mas na Figura 7.5 também existem atribuições de software.5 Cada
controller de superfície de controle tem um controller de flapes executando
nele (cada um constituindo uma instância de ControllerDeFlapes). De maneira
similar, a máquina de guiamento tem um controller de atitude executando
nela.6 Essas três peças de software são componentes de software da UML;

5. Esse diagrama de implantação é aproximadamente equivalente ao diagrama de fluxo de ar-


quitetura de Hatley e Pirbhai, 1988, ou o modelo de processador de Ward e Mellor, 1985.
6. Um controller de atitude controla os diversos ângulos de vôo do avião e não, como um dos
meus alunos pensou, o humor dos passageiros.
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 197

cada uma delas representada por um retângulo com pequenas barras abertas
em um lado.7

Figura 7.5 Diagrama de implantação para parte de um sistema


(simplificado) de controle de aeronaves.

O critério para a denominação de componentes de software da UML pa-


rece variar de local de trabalho a local de trabalho. Formalmente falando, um
componente de software na UML é qualquer elemento de software que tenha
tanto uma especificação abstrata (como uma interface) quanto uma personifi-
cação concreta (como um corpo).8 Tenho visto alguns locais de trabalho defi-
nindo um componente de software como qualquer unidade de software
separadamente compilável, incluindo: uma classe; uma biblioteca dinamica-
mente vinculada (dynamically linked library — DLL); um par de C++, com-
preendendo um arquivo de cabeçalho (header file) (.h) e um arquivo de código
(code file) (.cpp); ou uma interface IDL CORBA envolta em torno de um corpo
de código de herança. Outros locais de trabalho escolhem pacotes para serem
os componentes de software de diagramas de implantação, utilizando o sím-

7. Estranhamente, as “pequenas barras abertas em um lado” não têm qualquer objetivo cone-
xional nos diagramas da UML (embora os “pequenos pirulitos abertos” certamente tenham,
como veremos). Meu colega Stan Kelly-Bootle ressalta que pequenas barras abertas sempre
terão uma finalidade conexional. Não obstante, quando desenho um componente de software
em um quadro branco, substituo as barras por pequenas linhas — ou nada coloco.
8. Desenvolvo mais essa definição no Capítulo 15.
198 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

bolo de componente para descrever o pacote, em vez do símbolo da UML que


introduzi na seção 7.1.1.9
Mas, agora, vamos voltar à Figura 7.5. Uma interface de componente —
um pequeno “pirulito”, tal como os situados no canto esquerdo de controllerDe-
Flapes, mostra um tipo de serviço que um componente de software disponibi-
liza para o mundo exterior, via uma interface. Neste exemplo, mostrei
ServiçosDeManipulaçãoDeFlapes, uma interface que provê operações (tais como,
digamos, posicionarÂngulo) para a manipulação de flapes. (Outra interface po-
deria ser ServiçosDeTesteDeFlapes.)
Uma seta tracejada apontando para uma interface de componente repre-
senta comunicação. O iniciador da comunicação está na extremidade da cau-
da. Entretanto, a informação pode passar em ambas as direções, semelhante
aos argumentos de entrada e saída de uma mensagem. Por exemplo, na Figu-
ra 7.5, acrescentei um “ping” entre os componentes do controller mestre e de
back-up de flapes para ilustrar a aplicação de um estereótipo da UML para
uma seta de comunicação do diagrama de implantação. O ping permite a cada
componente verificar periodicamente o status operacional do outro. O estereó-
tipo «ping», portanto, pode ser definido para o protocolo, a freqüência e assim
por diante.
Às vezes, você tem de modelar explicitamente certos relacionamentos ar-
quiteturais, tais como quais os componentes de software executáveis em de-
terminados nós de hardware. A Figura 7.6 utiliza dois estereótipos para
consumar isso.
A Figura 7.7 modela um relacionamento menos específico, respondendo à
questão comum: "Que plataformas de hardware são compatíveis com um dado
componente de software?"
Os diagramas de implantação também são encontrados em ambientes de
negócio, tão freqüentemente quanto em locais de trabalho de tempo real — muito
embora “bem raramente” possam representar uma melhor descrição do que mi-
nha experiência. Por exemplo, na Figura 7.8, vemos um diagrama de implanta-
ção como parte de uma aplicação referente ao caixa automático de um banco. O
processadorATM (máquina ScroogeTeller86) se comunica via linkDeATM (da classe
WAN) com o servidorDeContaRegional (máquina DataBlast12A). No processador-
ATM, há um componente de software (CaixaAutomático) que controla “a entrega

9. Quando recentemente propus ao catedrático Sid Dijkstra — um internacionalmente festejado


expert em símbolos de pacotes da UML — que os símbolos de componentes e pacotes da UML
fossem fundidos em um único símbolo, ele ficou horrorizado. “Meilir, seu idiota — ele excla-
mou com veemência.” Você não sabe que os pacotes retratam as propriedades estáticas e lé-
xicas dos sistemas, enquanto os componentes retratam a estrutura dinâmica em run-time?
Oh, bem, não posso alcançar todos, suponho!
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 199

Figura 7.6 Forma de diagrama de implantação que mostra


quais componentes de software são executáveis
em determinados nós de hardware.

Figura 7.7 Uma forma menos específica de diagrama de implantação.


200 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

de dinheiro ao cliente”. Esse componente se comunica com outro componente


de software (ServidorDeDadosDeConta), que lida com os dados de conta via a
interface ServiçosDeConta.

Figura 7.8 Diagrama de implantação como parte de um


sistema de caixa automático de um banco.

7.2 Representando a Interface Humana


Esta seção trata de dois diagramas adicionais, um para layout e outro para
navegação de janelas. Embora esses dois tipos de diagrama não sejam partes
da UML convencional, eles são indispensáveis para a construção de um siste-
ma moderno típico com uma interface gráfica do usuário ( graphical user in-
terface — GUI) repleta de janelas.
O diagrama de layout de janelas, que discuto na seção 7.2.1, captura as
propriedades de cada janela individual. O diagrama de navegação de janelas,
que exploro na seção 7.2.2, captura as transições entre as janelas que formam
as rotas de navegação específicas à aplicação. A seção 7.2.3 é uma pequena di-
gressão na “orientabilidade a objeto” de interfaces gráficas do usuário.

7.2.1 O diagrama de layout de janelas


Conforme mostrado na Figura 7.9, o diagrama de layout de janelas correspon-
de sob muitas formas à janela atual que será apresentada para esta parte da
aplicação. Neste caso, a aplicação é um sistema de vendas.
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 201

Figura 7.9 Exemplo de um diagrama de layout de janelas.

Inicialmente no projeto — possivelmente durante a fase de criação de


protótipos — o diagrama exibirá os campos, botões e menus das janelas, mas
ele não será esteticamente correto. Por exemplo, os campos talvez estejam de-
salinhados e as cores e fontes talvez sejam arbitrárias. Mais tarde, a equipe
de desenvolvimento poderá aperfeiçoar a aparência do diagrama de forma que
ele se enquadre melhor às normas da GUI dos locais de trabalho. Por exemplo,
se o produto final tiver campos obrigatórios em azul-escuro, e campos opcio-
nais em branco, essa equipe pode ajustar o diagrama de layout de janelas para
diferenciar os dois tipos de campos.
Especialmente, durante a confecção dos protótipos, o diagrama de layout
de janelas poderá ser uma ferramenta improvisada e tosca. Naturalmente, al-
guns locais de trabalho desenvolvem suas próprias ferramentas em grandes
papéis de recados amarelos e os “colam” desajeitadamente em quadros bran-
cos. (Essa abordagem é denominada realização de protótipos de interface ao
usuário de baixa tecnologia.) Outros locais de trabalho mantêm seus diagra-
mas na forma lida por máquina, mas não necessariamente se utilizam de uma
ferramenta de construção de janelas personalizada.
202 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

O objetivo principal do diagrama de layout de janelas é prover uma es-


trutura para uma posterior especificação de desenho, que inclui validações
cruzadas de campos requeridos, sincronizações entre campos, verificações de
bancos de dados e assim por diante. Um diagrama de layout de janelas não
trivial pode ser a fonte de diversas páginas de especificações de janelas.

7.2.2 Diagrama de navegação de janelas


O objetivo de um diagrama de navegação de janelas é mostrar como os usuá-
rios podem passar de uma janela para outra ao longo de rotas expressivas e
importantes destinadas a certas aplicações. Muitas vezes, um diagrama de na-
vegação de janelas mostra as rotas de interação computador — homem para
um simples caso de uso.
O diagrama de navegação de janelas constitui uma adaptação direta do
diagrama de transição de tela (veja, por exemplo, Yourdon, 1989, p.392), que
é, em seu próprio termo, uma adaptação da estrutura do diagrama de estado
que descrevi no Capítulo 6.
A Figura 7.10 mostra o exemplo de um diagrama de navegação de janelas
para a parte relativa à colocação de preços em produtos participantes de um
sistema de vendas. Eu inseri estereótipos para os papéis específicos dos sím-
bolos do diagrama: «window» para janela; «cb» para botão de comando e «nav»
para rota de navegação entre janelas.
Partindo de um menu, o usuário chega à janela principal (ModificarLista-
DePreços) para modificar a lista de preço de um produto. Nesse ponto, ele pode
selecionar a opção Nova do menu, obtida sob o nome Arquivo, que o conduzirá
à janela ListaNovaDePreços para que ele inicie uma nova lista de preços. De for-
ma alternativa, ele pode selecionar a opção Aberta do menu, obtida sob o nome
Arquivo, que o conduzirá à janela ListaDePreçosAberta para que ele restaure
uma lista existente de preços. De qualquer forma, ele deve retornar à janela
ModificarListaDePreços, na qual poderá verificar os detalhes da lista de preços,
dando um clique no botão Detalhes. Ele não precisará retornar a partir da ja-
nela ModificarDetalhesDePreços (pelo fato de que, presumivelmente, ele poderá
sair da modificação completa da lista de preços, enquanto tiver essa janela
aberta).
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 203

Figura 7.10 Exemplo de um diagrama de navegação de janelas.

Um diagrama de navegação de janelas exibe somente uma abstração de


cada janela. (O diagrama de layout de janelas tem a tarefa de mostrar os de-
talhes de uma janela.) Ele mostra simplesmente os botões, os cliques duplos
e outros detalhes, que ocasionam as transições de uma janela para outra. De
maneira similar, ele mostra apenas os itens e subitens do menu que provocam
transições. A Figura 7.11 fornece um quadro dos símbolos do diagrama de na-
vegação de janelas.

Figura 7.11 Quadro de símbolos em um diagrama de navegação de janelas.


204 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A Figura 7.12 mostra um diamante de navegação, utilizado para indicar


um ponto de bifurcação entre duas rotas alternativas de navegação. Partindo
do menu principal (neste caso, uma aplicação de entrada de pedidos), o usuá-
rio chega a uma janela para a seleção de clientes. Presumivelmente, essa ja-
nela relaciona clientes (objetos da classe Cliente) em uma tabela, com
informação suficiente nas colunas (tal como nome e endereço) para que o
usuário reconheça cada cliente. O usuário pode então selecionar um cliente
com um mouse — para “enfocar” esse especial cliente presente na relação. De-
nominaremos esse cliente selecionado clienteSobAnálise.

Figura 7.12 Rotas alternativas de navegação.

Uma vez que o usuário tenha selecionado o clienteSobAnálise, o botão de


Novo Pedido torna-se habilitado. O ato de dar um clique nesse botão leva o
usuário à janela para acrescentar o novo pedido do clienteSobAnálise. Entre-
tanto, há dois grupos de clientes para essa companhia: domésticos e interna-
cionais. Este último grupo requer uma janela especial para a entrada de
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 205

pedidos, com todas as espécies de campos especiais pertinentes a assuntos al-


fandegários, cartas de crédito, permissões para exportação e outras coisas mais.
A aplicação deve levar o usuário para a janela de entrada de pedidos
mais apropriada para esse clienteSobAnálise. Eu ilustro esa bifurcação nave-
gacional existente no caminho como um diamante preto com duas rotas par-
tindo do mesmo ponto, com uma expressão de condição de guarda booleana em
colchetes indicando que rota será habilitada para um dado cliente.
No tocante ao desenho da interface humana, os diamantes de navegação
deverão ser utilizados moderadamente, porque uma utilização excessiva dos
mesmos poderá confundir o usuário. Entretanto, quando você precisa modelá-
lo, os diamantes e suas condições de guarda revelam-se muito expressivos.

7.2.3 Uma breve digressão: o que a orientação a objeto


tem em comum com a GUI?
Semelhante à própria orientação a objeto, as interfaces gráficas do usuário
(Graphical User Interface — GUI) têm dispertado diversas reações. Algumas
pessoas mais exaltadas vêem as GUIs como o prato principal da orientação a
objeto, exclamando: “Uau! GUIs!” Isso significa que temos objetos na tela.
Ei, somos orientados a objeto! — alguém pode exclamar. Mas outras pes-
soas, seres mal-humorados, reclamam que as interfaces gráficas do usuário
não deveriam nem mesmo ser mencionadas em um livro sobre orientação a ob-
jeto. Minha opinião é que, desde que a GUI moderna tem aspectos orientados
a objeto, tanto filosófica como praticamente, ela realmente faz parte de um li-
vro sobre OO. Daí, primeiro a filosofia, e em seguida a prática.
Um sistema que se apresenta a seus usuários como um conjunto coope-
rativo de janelas normalmente segue o paradigma da ação-objeto. Isso signi-
fica que um usuário seleciona um objeto com um simples clique de mouse (o
objeto então talvez seja mostrado como um ícone ou uma linha em uma tabela)
e então aplica uma ação ao mesmo (talvez via uma seleção de menu ou um cli-
que duplo de mouse).
O paradigma da ação-objeto traz à luz a orientação a objeto para a inter-
face humana. Eu afirmo isso com base na idéia de que primeiro identificar um
objeto e então solicitar a este para que faça algo corresponde exatamente a en-
viar uma mensagem, na qual primeiramente identificamos um objeto pelo seu
identificador e em seguida invocamos uma de suas operações.
Outro conceito orientado a objeto, o polimorfismo, poderá também ser im-
portante na interface humana. Como mencionei no Capítulo 2, você pode ilu-
minar (selecionar) um objeto em uma janela e em seguida “comunicá-lo para
206 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

que imprima por seus próprios termos”. A forma como ele imprime a si próprio
dependerá de o objeto selecionado tratar-se de uma planilha eletrônica, um
documento de texto ou um diagrama.
Pelo lado prático, o desenho orientado a objeto é uma abordagem muito
apropriada para construir a interface humana de um sistema. Você pode de-
senhar a interface do usuário exatamente da mesma forma que qualquer ou-
tra aplicação orientada a objeto. Por exemplo, quando uma janela da classe
JanelaAtualizável fecha, ela envia uma mensagem para uma janela SalvarIgno-
rarOuCancelar para prevenir o usuário que as alterações talvez sejam perdidas.
(A classe JanelaAtualizável pode derivar por herança da classe Janela.)
Os objetos da janela ainda podem trocar mensagens com objetos de negó-
cio. Como simples exemplo, uma janela da classe ObtençãoDeItemDeProduto
pode enviar uma mensagem para um objeto da classe ItemDeProduto a fim de
exibir a relação de VendaPorData de um particular item de produto que esteja
na janela.
Assim, tanto no sentido filosófico (ação-objeto) quanto no sentido prático
(desenho orientado a objeto), as interfaces gráficas do usuário ainda terão um
lugar proeminente na etapa orientada a objeto durante muito tempo no futuro.

7.3 Resumo
Este capítulo apresentou a notação de modelagem adicional para o desenvol-
vimento de sistemas, especialmente aqueles com uma arquitetura de sistema
sofisticada ou uma interface gráfica do usuário.
A UML oferece dois diagramas complementares para capturar os aspec-
tos relativos ao software e hardware da arquitetura de sistemas. O primeiro
é o diagrama de pacote, que representa agrupamentos exclusivamente de ele-
mentos de software. Esse diagrama é apropriado para modelar a estrutura de
alto nível do software a ser implementado.
O segundo diagrama é o diagrama de implantação, que retrata as unida-
des de tecnologia (especialmente hardware, tal como processadores e armaze-
namento de massa, juntamente com seus vínculos físicos de comunicação) nas
quais o sistema será implementado. O diagrama de implantação pode também
modelar como o software será distribuído pelas unidades de tecnologia esco-
lhidas, sobrepondo componentes de software e suas interconexões em um dia-
grama de implantação representativo de pura tecnologia física (tal como
processadores).
Um componente de software é um corpo de código em run-time com uma
interface bem-definida que provê serviços a outros componentes. Na prática,
um componente de software em um diagrama de implantação pode ser uma
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 207

biblioteca vinculada dinamicamente, um pacote ou até mesmo (raramente) um


único objeto. Alguns locais de trabalho, certo ou erradamente, selecionam pa-
cotes para serem os componentes de software mostrados em seus diagramas
de implantação.
O diagrama de layout de janelas define o conteúdo de uma janela a ser
apresentada como parte da interface humana de um sistema. Ele aproxima o
layout das janelas, mas não contém todo o eventual refinamento estético. O
diagrama de layout de janelas serve tanto como um protótipo da janela mais
recente como uma estrutura sobre a qual especificamos validações de campos,
comportamento janela/campo, mensagens entre objetos de negócio e assim por
diante.
O diagrama de navegação de janelas, que retrata as rotas expressivas e
significativas para aplicações entre janelas, habilita os desenhistas e usuários
de interfaces a exploração da navegação em interfaces antes de o sistema ser
construído. Ele abstrai cada janela como um retângulo em forma de esqueleto
que suporta os botões de navegação de janelas. As rotas entre janelas apare-
cem como setas, anotadas onde se fizer necessário com seleções a partir de op-
ções em menus. Utilizo estereótipos da UML para indicar janelas, botões e
rotas de navegação. Também utilizo um diamante de navegação em que as ro-
tas de navegação se separam, e coloco expressões de condições de guarda ao
lado das rotas de saída do diamante.

7.4 Exercícios
1. O particionamento de software em unidades de tecnologia pode ocorrer
em diversos "níveis de granularidade". Por exemplo, certo subsistema in-
teiro poderia residir em um processador, enquanto outro subsistema fica-
ria em outro processador. Você consegue pensar em outros níveis de
particionamento, especialmente alguns que são melhores que o nível do
subsistema, que seriam possíveis com um sistema orientado a objeto?
(Dica: de que forma você poderia particionar objetos e as classes nas
quais eles pertencem?)
2. O fragmento de um diagrama de navegação de janelas na Figura 7.13
mostra três janelas a partir da interface de um sistema de informação --
cliente. Inicie com a janela SelecionarCliente e, julgando pelas rotas de na-
vegação e dos nomes das janelas e botões, adivinhe o que acontecerá em
cada janela mostrada. Para quais diagramas você se voltaria para confir-
mar ou refutar sua especulação?
208 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 7.13 Três janelas como parte de um diagrama de navegação de janelas.

7.5 Respostas
1. Uma possibilidade é que os objetos de determinada classe estariam dis-
tribuídos em processadores. Por exemplo, poderíamos ter outra em Paris,
outra em Tóquio, uma máquina no Rio de Janeiro e outra em Buenos Ai-
res. Os objetos da classe Cliente poderiam, então, ser distribuídos por es-
sas máquinas, pela razão óbvia de eficiência: Gostaríamos de, por
exemplo, acessar rapidamente nossos clientes japoneses na máquina de
Tóquio. O código para a classe Cliente presumivelmente seria completa-
mente replicado em todas essas quatro máquinas. O particionamento de
uma população de objetos como essa é denominado particionamento hori-
zontal.
Os objetos poderiam também ser totalmente replicados, talvez para
permitir que o escritório de Paris rapidamente tivesse acesso às informa-
ções sobre “les Hoosiers”. Entretanto, essa redundância complicaria a
manutenção de uma consistência mútua, por meio de cópias do mesmo
objeto.
Cap. 7 ARQUITETURA E DIAGRAMAS DE INTERFACE 209

Você até mesmo poderia decompor um objeto de negócio individual em


partes que se conformariam a diferentes máquinas. Esse é um tipo de
particionamento muito fino, mas que poderia se mostrar de utilidade em
algumas circunstâncias. Por exemplo, digamos que você tenha desenhado
uma classe Cliente para uma aplicação que trate de remessas e finanças.
Os objetos de Cliente iriam, portanto, conter características com res-
peito a informações financeiras, informações sobre remessas, informações
de crédito, e assim por diante.
Mesmo ao desenhar aplicações não distribuídas, você deveria tentar
dividir uma grande classe (como Cliente, que provavelmente contém uma
profusão de atributos) em diversas classes de “característica”, utilizando
a construção de composição para correlacionar seus objetos. Por exemplo,
você poderia criar três características para cliente: FinançasDeCliente, Re-
messasDeCliente e PerfilDeCliente. Um objeto da classe Cliente conteria re-
ferências aos objetos de todas as três classes. Como vimos na seção 7.1.1,
essas classes de característica para cliente poderiam, então, ser agrupa-
das por conveniência em um pacote denominado PacoteDeCliente.
Se o escritório no Rio fosse puramente destinado a vendas, daí então
talvez os objetos dessa localidade raramente receberiam perguntas para
o fornecimento de informações financeiras sobre os clientes. Portanto, a
classe FinançasDeCliente, e todas as suas instâncias, poderiam ser remo-
vidas da máquina no Rio para que se economizasse espaço (e, possivel-
mente, para aperfeiçoamento da segurança). Uma mensagem de um
objeto Cliente na máquina no Rio para um objeto FinançasDeCliente iria,
portanto, requerer comunicação em rede. (Na verdade, a segurança da
rede talvez seja mais fraca que a segurança da máquina no Rio!)
A divisão de objetos assim realizada é denominada particionamento
vertical. O particionamento vertical introduz mais uma vez problemas de
consistência (por exemplo, ter certeza de que as peças fisicamente sepa-
radas serão mantidas consistentes), e é normalmente implementado de
forma que pelo menos uma máquina suporte objetos integrais não parti-
cionados.
Uma terceira possibilidade é que uma classe resida em uma máquina
e seus objetos residam em outra. Nesse caso, a segunda máquina pode se
especializar em manter e manusear volumes grandes de informações
(como um denominado recurso de banco de dados), ao passo que a primei-
ra máquina pode, por sua vez, se especializar em processamento ou num-
ber-crunching (processo para se fazer uma porção de cálculos
210 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

matemáticos em um curto espaço de tempo). Para executar uma mensa-


gem, o objeto apropriado é transferido da segunda máquina para a pri-
meira. Para que essa abordagem consiga méritos no tocante à eficiência,
as duas máquinas normalmente deverão estar próximas uma das outra.
Muito embora existam diversas outras maneiras para particionar o
software orientado a objeto, essas três formas citadas são as mais co-
muns. Incidentalmente, observe novamente quão importante foi, nos parágra-
fos anteriores, conservar a distinção entre o termo classe e o termo objeto.
2. Partindo do menu principal na Figura 7.13, o usuário chega a uma janela
para a seleção de clientes, que opera similarmente à janela Selecionar-
Cliente da Figura 7.12. Mas, assim que o usuário seleciona um cliente (di-
gamos, clienteSobAnálise), o botão de Alteração torna-se habilitado.
Quando se dá um clique, o cliente é transportado para a janela Alterar-
Cliente para se modificarem as informações sobre o mesmo.
Quando a janela AlterarCliente é exibida, o usuário poderá rever todas
as informações externamente visíveis sobre o cliente, representadas pelo
objeto clienteSobAnálise, e poderá modificar qualquer um desses campos
atualizáveis existentes na janela. Ele deverá retornar dessa janela para
a janela de seleção (conforme indicado pela seta de duas vias). Em outras
palavras, ele apenas consegue fechar a janela AlterarCliente e retornar ao
enfoque da janela SelecionarCliente e não pode abrir outra janela, tal como
a de acrescentar mais clientes.
A janela AcrescentarCliente, designada para acrescentar um novo clien-
te, opera similarmente à janela já existente para a alteração de clientes.
Entretanto, o botão Novo na janela SelecionarCliente presumivelmente se
encontra sempre habilitado, quer o usuário tenha um cliente selecionado
ou não. (Não há qualquer necessidade de selecionar um cliente existente
a fim de acrescentar outro.)
Parte dessa explicação é especulativa. Para confirmar minhas especu-
lações na prática, deveríamos examinar os diagramas de layout de jane-
las para as três janelas que relacionam os campos que cada janela
contém. Mais importante ainda, as definições por detrás dos diagramas
nos revelam o que cada janela faz quando ela abre e fecha, quando cada
um de seus botões se torna habilitado e desabilitado, o que acontece
quando se dá um clique em um botão, o que as regras de validação repre-
sentam para os campos e assim por diante. Juntamente com o diagrama
de navegação de janelas, os diagramas de layout de janelas completariam
nosso entendimento deste fragmento da interface gráfica do usuário.
“A lógica de nossa compreensão contém as leis
do pensamento correto sobre uma
classe particular de objetos”

Immanuel Kant, Critique of Pure Reason

P arte III — Os Princípios do


Desenho Orientado
a Objeto
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
PARTE III — OS PRINCÍPIOS DO DESENHO ORIENTADO A OBJETO

O s capítulos da Parte III abordam os princípios existentes por trás de um


bom desenho orientado a objeto e os critérios por meio dos quais você
pode avaliar a qualidade de seus desenhos. Em outras palavras, a Parte III
levanta a questão: o que faz com que tenhamos um bom desenho e um mau
desenho orientado a objeto?
A questão é certamente expressiva, pois já vi código orientado a objeto em
uma aplicação extensível e modificável e código orientado a objeto em aplica-
ção horrível, complicada e terrível de se manter. Uma vez que a programação
das duas aplicações era similar, e os requisitos que as aplicações estavam ten-
tando implementar eram similares, a variação deve ter sido devido às estru-
turas de desenho das aplicações. (Então, novamente, talvez o desenhista da
segunda aplicação também fosse horrível, complicado e terrível).
Começo o Capítulo 8 estabelecendo um contraste entre a estrutura de en-
capsulamento do software orientado a objeto com a do software convencional.
A seguir, construo a idéia de congeneridade (connascence) segundo o princípio
do encapsulamento. A congeneridade, que corresponde a uma generalização
do acoplamento e coesão do desenho estruturado para estruturas de encapsu-
lamento mais complexas, é uma medida de quão bem um desenhista tenha ex-
plorado o potencial de encapsulamento oferecido pela orientação a objeto.
Classes em níveis distintos de abstração de desenho têm distintas carac-
terísticas e propriedades de desenho. Elas também têm diferentes graus de
reusabilidade. O Capítulo 9 delineia os domínios de classes que você provavel-
mente encontrará em uma aplicação, e o que as classes representam em cada
domínio. O capítulo introduz uma metrificação quantitativa, o grau de depen-
dência de uma classe, e mostra como isso está relacionado com os domínios de

211

Parte III — Os Princípios do Desenho Orientado


212 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

classes. O capítulo utiliza a idéia de domínios e o grau de dependência para


avaliar três tipos de coesão de classe. (A coesão de classe indica quão intima-
mente consolidados estão os atributos e operações de uma dada classe, e se
“fazem parte em conjunto” dessa classe).
Os capítulos 10 e 11, que exploram os fundamentos de desenho da orien-
tação a objeto, estão na parte central da Parte III. O Capítulo 10 introduz as
idéias de estado-espaço e o comportamento de classes, juntamente com os con-
ceitos de invariantes de classe e precondições/pós-condições, que formam as
bases do desenho por contrato. O Capítulo 11 examina as propriedades das
subclasses em termos de estados-espaços, juntamente com os princípios de de-
senho do tipo comportamento de conformidade e fechado, que guiam o desen-
volvimento de hierarquias de classe robustas.
No Capítulo12, utilizo conceitos dos capítulos iniciais da Parte III para
identificar e remover os perigos que espreitam em algumas construções orien-
tadas a objeto, inclusive os estimados mecanismos de herança e polimorfismo.
O Capítulo 13 disseca dois desenhos reais para ilustrar técnicas de organiza-
ção de operações que podem melhorar a resiliência do desenho de classe. O
Capítulo 14 analisa mais profundamente a questão sobre o que constitui um
bom desenho orientado a objeto, inspecionando a qualidade de uma interface
de classe e revelando os critérios para que uma classe implemente apropria-
damente um tipo de dado abstrato.
E ncapsulamento e
congeneridade
ENCAPSULAMENTO E CONGENERIDADE
8.Encapsulamento e Congeneridade

E ste capítulo aborda as duas propriedades fundamentais da estrutura de


sistemas orientados a objeto: encapsulamento e congeneridade. Embora
ambas as propriedades da estrutura de software estejam presentes nos siste-
mas convencionais, a orientação a objeto, com suas novas complexidades, ele-
va de forma considerável a importância das mesmas. Os processos de
compreensão e manutenção do software orientado a objeto — mesmo o valor
da orientação a objeto em si — repousam fundamentalmente no encapsula-
mento e na congeneridade.
Na primeira seção deste capítulo discuto o encapsulamento; na segunda,
discuto a congeneridade e, a seguir, exploro como um bom software orientado
a objeto depende de uma combinação entre um bom encapsulamento e uma
boa congeneridade.

8.1 Estrutura de Encapsulamento


Conforme mencionei no Capítulo 1, o software emergiu na década de 40, a
partir de um pântano primitivo, como uma coleção de criaturas unicelulares
conhecidas como instruções de máquina. Mais tarde, essas criaturas evoluíram
para outras criaturas unicelulares conhecidas como linhas de código de um pro-
grama montador (assembler). Porém, logo despontou uma estrutura mais nobre,
na qual várias linhas de código eram reunidas em uma unidade de procedimento
com um único nome. Esta constituía a sub-rotina (ou procedimento), propiciando
exemplos tais como calcularReembolsoDeEmpréstimo, e a estrela do “Hall da
Fama das Sub-rotinas” de todos os tempos, calcularRaizQuadrada.
A sub-rotina introduziu o encapsulamento no software. Tratava-se do en-
capsulamento das linhas de código em uma estrutura um nível acima que o
do próprio código. A sub-rotina foi uma maravilha de sua era. Conforme men-

213
214 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

cionei no Capítulo 1, ela economizou uma preciosa memória de máquina ao se-


parar do conjunto maior várias instruções, e também poupou a memória hu-
mana provendo aos programadores um único termo (tal como
calcularReembolsoDeEmpréstimo) para se referir a várias linhas de código. Não
é de surpreender que o advento da sub-rotina tenha induzido os programado-
res ao êxtase e tenha dado origem às “festas de criação de códigos que vara-
ram as madrugadas”.

8.1.1 Níveis de encapsulamento


Eu chamo o nível de encapsulamento da sub-rotina como encapsulamento de
nível-1 (level-1). (O código primário, isento de encapsulamento, tem um encap-
sulamento de nível-0 [level-0].) A orientação a objeto introduz um nível ulte-
rior de encapsulamento. A classe (ou objeto) é uma reunião conjunta de
sub-rotinas (conhecidas como operações) em uma estrutura de um nível ainda
mais alto. Uma vez que as operações, por se tratarem de unidades de proce-
dimentos, já se encontram no encapsulamento de nível-1, a classe está no en-
capsulamento de nível-2. Veja a Figura 8.1.

Figura 8.1 Três níveis de encapsulamento exibidos por construções de software.


Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 215

Minha analogia entre organismos e estruturas de software, embora dis-


tante de ser profunda, não é totalmente gratuita. Os módulos procedurais,
tais como os encontrados no desenho estruturado, não compreendem a reusa-
bilidade que as pessoas esperavam.1 Classes não têm par, sendo muito melho-
res que procedimentos. As classes se aproximam dos órgãos biológicos por
suas capacidades de serem transplantadas, de aplicação para aplicação. E os
componentes, que discuto no Capítulo 15, ainda se saem melhor do que as
classes!
“Mas por que parar no encapsulamento de nível-2?”, você perguntaria.
Uma boa questão! Já temos visto encapsulamentos de nível-3 e nível-4, nos
quais as classes são agrupadas em estruturas de nível mais alto, tais como os
pacotes e componentes que discuto nos Capítulos 7 e 15. Excluindo-se as es-
truturas de nível-3 e de nível mais alto, apenas algumas classes (ou partes de
suas interfaces) são feitas visíveis.
As classes de negócio em uma grande organização orientada a objeto são
freqüentemente agrupadas “horizontalmente” em estruturas de nível-3 pela
respectiva área de assunto, muito similarmente ao que fizemos no Capítulo 7.
Por exemplo, uma companhia aérea poderá ter uma área de assunto relacio-
nada a passageiro, outra para aeroporto, outra para quadro de funcionários e
uma outra para aeronave. Uma vez que classe relacionada a programa de vôos
habituais de um passageiro não teria muito a ver com uma classe para inven-
tário de aeronaves, as duas classes poderiam ser encapsuladas em dois paco-
tes diferentes de nível-3.
As estruturas de nível-3 podem ser ainda agrupadas “verticalmente”.2
Por exemplo, as classes que trabalhariam em conjunto para implementar o
plano de ação para AperfeiçoamentoDePassageiroHabitual poderiam incluir:
Passageiro (especialmente suas preferências e características de PassageiroHa-
bitual), Reserva, Etapa (de uma ViagemAérea), ConfiguraçãoDeAssentos NaAero-
nave, Assento, e classes mais baixas, tais como ListaDeEsperaPriorizada, Data,
Hora e assim por diante.
(A propósito, um plano de ação para transferir os passageiros habituais
que solicitaram um tipo de bônus, da classe econômica para a primeira classe,

1. O Módulo do Clube Mensal ofereceu uma ilustração disso. Após tornarem-se sócios do clube,
alguém poderia encomendar os mais recentes desenvolvimentos em módulos procedurais ex-
tremamente reutilizáveis. Infelizmente, o clube logo saiu dos negócios.
2. Um grupo horizontal de classes compreende classes do mesmo domínio que não necessaria-
mente interagem. (Os pacotes normalmente exibem essa estrutura.) Um grupo vertical de
classes interage para implementar uma atividade de negócio; elas normalmente são prove-
nientes de diversos domínios. (Os componentes normalmente exibem essa estrutura.) Para de-
talhes adicionais sobre quais classes pertencem a quais domínios, veja a seção 9.1.
216 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

é disparado, digamos, 72 horas antes do embarque. O sistema atribui a cada


passageiro interessado na melhora de qualidade de seu vôo uma prioridade de
acordo com a combinação de certos fatores tais como PassageiroHabitual.status,
Reserva.dataDeEfetivação, Reserva.baseDeTarifa, e assim por diante. Aos passa-
geiros de mais alta prioridade é concedido esse tipo de bônus, na ordem des-
cendente, começando com os passageiros mais próximos e prosseguindo até
que os assentos disponíveis sejam todos preenchidos.)

8.1.2 Critérios de desenho governando níveis interativos


de encapsulamento
A Tabela 8.1 resume alguns critérios convencionais de desenho estruturado
em termos dos níveis de encapsulamento da seção 8.1.1. Ela mostra quais cri-
térios se aplicam a cada par de níveis de encapsulamento. Por exemplo, a coe-
são é uma medida clássica da qualidade do relacionamento entre um
procedimento (uma construção de nível-1) e as linhas de código (construções
de nível-0) no interior do procedimento. Eu descrevo sucintamente no pará-
grafo cada um dos retângulos da tabela e as definições.

Tabela 8.1
Critérios de desenho estruturado (ou nível-1) que governam inter-
relacionamentos entre elementos em cada par de níveis de encapsulamento.

PARA: construção de nível-0 construção de nível-1


(linha de código) (procedimento)
DE:
construção de nível-0 Programação estruturada Fan-Out
(linha de código)
construção de nível-1 Coesão Acoplamento
(procedimento)

Os princípios da programação estruturada governam o relacionamento


entre uma linha de código e outras linhas de código dentro do mesmo proce-
dimento. Os termos “fan-out”, coesão e acoplamento são provenientes do dese-
nho estruturado.3

3. Veja, por exemplo, Page-Jones, 1988, e Yourdon e Constantine, 1979, para discussões deta-
lhadas sobre fan-out, coesão e acoplamento.
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 217

• “Fan-out” é uma medida do número de referências para outros proce-


dimentos por linhas de código dentro de um dado procedimento.
• Coesão é uma medida da “coerência” das linhas de código, dentro de
um dado procedimento, para atingir o objetivo desse procedimento.
• Acoplamento é uma medida do número e da resistência das ligações
entre procedimentos.

A Tabela 8.2 é um prolongamento da Tabela 8.1 para incluir o encapsu-


lamento de nível-2. Note que, embora a seção original (nível-0 e nível-1) per-
maneça basicamente a mesma, o encapsulamento de nível-2 fornece-nos mais
cinco retângulos para serem nomeados.
A coesão de classe é um sinônimo óbvio da coesão de um procedimento,
mas ela se encontra em um nível de encapsulamento mais alto. Ela refere-se
à coerência de um conjunto de operações ( e atributos) para atingir o objetivo
da classe. O acoplamento de classe é uma medida do número e da resistência
das ligações entre classes.
Embora os outros três retângulos não tenham nomes, poderíamos nomeá-
los (ou poderíamos desencavar nomes das profundezas místicas da literatura
orientada a objeto). Dessa forma, teríamos nove nomes. E, caso incluíssemos
o encapsulamento de nível-3, teríamos dezesseis nomes.
Mas já basta de tantos nomes! Quando o número de partículas funda-
mentais da física explodiu, os físicos começaram a perguntar a si mesmos se
essas partículas eram, afinal de contas, assim tão fundamentais. Quando o
número de critérios fundamentais do desenho explodir desse jeito, talvez de-
vamos tentar encontrar um critério mais profundo do que todos esses crité-
rios. Se não conseguirmos encontrar um critério como esse, então, devemos
nos dedicar a elementos de software em todos os níveis de encapsulamento —
até mesmo no nível-5, caso formos, algum dia, agraciados com o mesmo.
Na próxima seção, proponho este critério: congeneridade.
218 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Tabela 8.2
Prolongamento da Tabela 8.1 para a inclusão de encapsulamento
de nível-2 (classes).

PARA: construção de nível-0 construção de nível-1 construção de nível-2


(linha de código) (operação) (classe)
DE:
construção de nível-0 Programação Fan-out —
(linha de código) estruturada de mensagem
construção de nível-1 Coesão Acoplamento —
(operação)
construção de nível-2 — Coesão de classe Acoplamento
(classe) de classe

8.2 Congeneridade
Congeneridade (connascence), palavra derivada do latim, significa “terem nas-
cidos juntos”. Uma sugestão para esse significado poderia ser “destinos entre-
laçados na vida”. Dois elementos de software que são congêneres (ou que
nasceram ao mesmo tempo) são gerados desde alguma necessidade afim —
talvez durante a análise de requisitos, no desenho, ou na programação — e
compartilham um destino devido a pelo menos uma razão. A seguir tem-se a
definição de congeneridade da forma como ela é aplicada ao software:

A congeneridade entre dois elementos de software A e B significa

1. que você pode postular alguma mudança de A, que pediria que B


fosse mudado (ou, no mínimo, cuidadosamente verificado) a fim de
preservar a exatidão global;

2. que você pode postular alguma mudança, que pediria que tanto A
como B mudassem juntos para preservar a exatidão global.

Nesta seção, exploro as variedades da congeneridade. Meu objetivo prin-


cipal é apresentar o conceito como uma forma geral de avaliar decisões perti-
nentes a desenho em um desenho orientado a objeto, mesmo se esse desenho
incluir estruturas de encapsulamento de nível-3 ou nível-4.
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 219

8.2.1 Variedades de congeneridade


Iniciarei com um exemplo simples, não orientado a objeto, de congeneridade.
Vamos fazer com que o elemento de software A seja a linha única de um código
convencional, declarando que:

int i: // linha A

e o elemento B a declaração:

i := 7; // linha B

Há pelo menos dois exemplos de congeneridade entre A e B. Por exemplo,


na situação (improvável) em que A fosse mudado para char i; então, B certa-
mente também teria de ser mudado. Isto é congeneridade de tipo. Igualmente,
se A fosse mudado para int j;, daí, B deveria ser mudado para j := 7;. Isto é
congeneridade de nome.
Agora, algumas variações sobre o tema da congeneridade: O exemplo aci-
ma de i, nas linhas A e B, exibiu uma congeneridade explícita, que na verdade
é a congeneridade detectável por um bom editor de texto. Em outras palavras,
é a congeneridade que salta para fora da página e diz, “Este elemento é con-
gênere com aquele outro”.
Outro tipo de congeneridade, entretanto, é implícita. Por exemplo, em
uma rotina de montagem (assembler) eu cheguei a ver no passado:

X: JUMP Y+38
...
Y: CLEAR R1
... // 38 bytes de código ora existentes
CLEAR R2 // Esta é a instrução que “salta” de X para Z
...

Existem 38 bytes entre CLEAR R1 e CLEAR R2. Exatamente 38 bytes! Essa


congeneridade de posição entre essas duas inocentes instruções em Y e Z é for-
çada sobre elas pelo desagradável salto na linha X. Muito embora a necessi-
dade de uma compensação de, precisamente, 38 bytes não fique aparente no
código após a linha Y, o infortúnio assolaria qualquer um que inserisse outra
instrução em algum lugar nesses 38 bytes.4

4. Isso, a propósito, é exatamente o que um programador de manutenção fez. Na execução se-


guinte, o sistema parou (crashed) brevemente após a linha X ter sido executada.
220 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Evidentemente, a clareza e a falta de nitidez não são nem binárias e nem


absolutas. Contrariamente, a congeneridade tem um espectro de clareza.
Quanto mais implícita é a congeneridade, mais tempo se consome e maior o
custo para ela ser detectada (a menos que ela esteja bem documentada em um
local óbvio). A congeneridade que transpõem enormes distâncias textuais em
uma especificação de biblioteca de classes, ou em outro tipo de documentação,
muito provavelmente consumirá bastante tempo e será difícil de ser descoberta.
Observe que:

1. Dois elementos de software não precisam se comunicar entre si a fim de


serem congêneres. (Vimos um exemplo disso na congeneridade de posição
entre as linhas Y e Z na rotina de montagem anterior.)
2. Algumas formas de congeneridade são direcionais. Se o elemento A refe-
rir-se ao elemento B explicitamente, então A e B serão unidirecionalmente
congêneres (o sentido indo de A para B). Muitos exemplos de congenerida-
de de nome são direcionais — por exemplo, a congeneridade de nome in-
troduzida quando uma classe é herdeira de outra. Se o elemento B
também referir-se ao elemento A explicitamente, então A e B serão bidi-
recionalmente congêneres.
3. Algumas formas de congeneridade são não direcionais. Os elementos A e
B seriam não direcionalmente congêneres caso qualquer um dos dois não
se referisse ao outro. Por exemplo, A e B são congêneres se eles utilizarem
o mesmo algoritmo, embora nenhum dos dois se refira absolutamente ao
outro.

Uma grande parte da congeneridade que descrevo acima é congeneridade


estática. Ou seja, congeneridade aplicada ao código das classes que você escre-
ve, compila e vincula. Faz parte da congeneridade que você pode estimar a
magnitude a partir da estrutura léxica da criação de códigos.
A lista a seguir (que não é extenuante) fornece algumas variedades a
mais de congeneridade estática.

Congeneridade de nome
Vimos isso no primeiro exemplo (linhas A e B) anterior, no qual duas variáveis
de programação precisam ter o mesmo nome a fim de referir-se à mesma coi-
sa. Outro exemplo: uma subclasse que utilize uma variável herdada de sua su-
perclasse deve obviamente utilizar o mesmo nome para a variável que o
utilizado pela superclasse. Se o nome for alterado na implementação da super-
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 221

classe, então ele deverá também ser trocado na subclasse se a exatidão tiver
de ser preservada.

Congeneridade de tipo ou classe


Também vimos a congeneridade de tipo no exemplo com o tipo de dado int. Se
for atribuído a i o valor 7 na linha B, então i deverá ser declarado como do tipo
int na linha A.

Congeneridade de convenção
Digamos que a classe NúmeroDeConta tenha instâncias nas quais os números
de conta positivos, caso de 12345, pertençam a pessoas; os números de conta
negativos, como –23456, pertençam a empresas, e 00000 pertença a todos de-
partamentos internos. O código será salpicado com assertivas como:

if ordem.númeroDaConta > 0
then...

Há uma congeneridade de convenção entre todos os elementos de softwa-


re que entram em contato com um número de conta. A menos que essa con-
venção do significado de NúmeroDeConta seja condensada, esses elementos
talvez se disseminem pelo sistema.5
O hominóide do Capítulo fornece-nos um outro exemplo. Digamos que Ho-
minóide tivesse o atributo direção. Ela, direção, poderia ser representada de
várias formas, e como exemplo, vejamos:

0 = norte; 1 = leste; 2 = sul; 3 = oeste


N = norte; E = leste; S = sul; W = oeste
0 = norte; 90 = leste; 180 = sul; 270 = oeste

Todo cliente de Hominóide que utilizar o atributo ficará exposto à conven-


ção escolhida para a representação de direção; isto criará uma formidável con-
generidade de convenção. Por conseguinte, é importante escolher uma
convenção decente (digamos, a terceira) para representar a direção.

5. Isso corresponde na verdade a uma congeneridade de “convenção de significado/valor”. Ela é


similar ao acoplamento híbrido em desenho estruturado e tem provocado muitos problemas
de manutenção em sistemas.
222 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Congeneridade de algoritmo
A congeneridade de algoritmo é similar à congeneridade de convenção. Exem-
plo: um elemento de software insere símbolos em uma tabela de remodelagem
(hash-table). Outro elemento está à procura de símbolos na tabela. Evidente-
mente, para isso funcionar, ambos devem utilizar o mesmo algoritmo de re-
modelagem. Outro exemplo: os algoritmos de codificação e verificação para os
dígitos de cheques em um número de conta de um cliente.
Um exemplo bizarro de congeneridade de algoritmo que presenciei recen-
temente foi causado por um bug na implementação de um algoritmo de ope-
rações de uma classe. Supunha-se que esta operação fosse retornar uma série
de valores, classificados na ordem ascendente. Mas, devido ao bug, os dois úl-
timos valores da série estavam sempre trocados. Por causa da ausência de có-
digo-fonte, ninguém foi capaz de corrigir o bug.
Dessa forma, o “dilema” era o seguinte: qualquer classe que invocasse
essa operação tinha um código incorporado à mesma que recambiava nova-
mente os dois valores “atacados” da série. Isso proporcionou uma terrível con-
generidade de algoritmo, que afetou seriamente a todos quando o defeito
original foi finalmente corrigido. Conseqüentemente, todos os trechos de códi-
go (em mais de quarenta lugares) tiveram de ser encontrados e removidos.

Congeneridade de posição
A maioria dos códigos em uma unidade de procedimento tem congeneridade
de posição: cada duas linhas de código que devem ser executadas na correta
seqüência de execução, elas devem aparecer na exata seqüência léxica na lis-
tagem. Existem diversos tipos de congeneridade de posição, que incluem a se-
qüencial (“devem aparecer na ordem correta”) e adjacente (“devem aparecer
próximas uma da outra”). Outro exemplo de congeneridade de posição é a con-
generidade em uma mensagem entre os argumentos formais no remetente e
os argumentos reais no destinatário. Na maioria das linguagens, você deve es-
pecificar argumentos reais na mesma seqüência dos argumentos formais.
A congeneridade dinâmica é a congeneridade baseada no modelo de exe-
cução do código de execução — os objetos, preferentemente às classes, se as-
sim você desejar. Essa congeneridade, da mesma forma, tem diversas
variedades:

Congeneridade de execução
A congeneridade de execução é o equivalente dinâmico da congeneridade de
posição. Ela se apresenta em diversos tipos, incluindo a seqüencial (“deve ser
executada em uma dada ordem”) e adjacente (“deve ser executada sem qual-
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 223

quer execução intermediária”). Existem muitos exemplos de congeneridade de


execução, inclusive a inicialização de uma variável antes de utilizá-la, altera-
ção e leitura de valores das variáveis globais na seqüência correta, e a espe-
cificação e os procedimentos de testes de valores para sinalização.

Congeneridade temporal
A congeneridade temporal manifesta-se na maioria das vezes em sistemas de
tempo real. Por exemplo, uma instrução para desligar um aparelho de raio-X
deve ser executada em um período de n milissegundos com relação à instrução
para ligá-lo. Essa restrição temporal permanece verdadeira, não importando
quantas vezes o sistema operacional necessite reentrar a tarefa ControllerDe-
Raio-X com o intuito de levar a cabo a carga de funcionamento de suas outras
tarefas.

Congeneridade de valor
A congeneridade de valor normalmente envolve alguma restrição aritmética.
Por exemplo, durante a execução de um sistema, o indicadorInferior de um
buffer circular jamais poderá ser superior ao indicadorSuperior (segundo as re-
gras da aritmética de módulos). Igualmente, os quatro cantos de um retângulo
devem preservar certo relacionamento geométrico para seus valores: você não
consegue simplesmente mover um canto e reter um retângulo perfeito.
A congeneridade de valor é notória por ocorrer quando dois bancos de da-
dos mantêm a mesma informação redundantemente, muitas vezes em forma-
tos diferentes. Nessa situação, o software de procedimento tem de conservar
uma ponte de consistência entre os bancos de dados para assegurar que qual-
quer dado duplicado tenha valores idênticos em cada banco de dados. A ma-
nutenção desse software, o qual está provavelmente executando desastrados
traslados de formatos, pode ser embaraçosa.

Congeneridade de identidade
Um exemplo de congeneridade de identidade é proporcionado por uma restri-
ção típica em um sistema orientado a objeto: dois objetos, obj1 e obj2, cada um
dos quais com uma variável apontando para outro objeto, devem sempre apon-
tar para o mesmo objeto. Isto é, se obj1 aponta para obj3, então obj2 deve
apontar para obj3. (Por exemplo, se o informe de vendas refere-se à planilha
eletrônica de março, então o relatório de operações forçosamente deve referir-
se à mesma planilha eletrônica.) Nessa situação, os obj1 e obj2 têm congene-
ridade de identidade; ambos devem referir-se ao mesmo (ou melhor, ao
idêntico) objeto.
224 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

8.2.2 Contrageneridade
Até o momento, tacitamente equiparei a congeneridade com “igualdade” ou
“afinidade”. Por exemplo, duas linhas de código têm congeneridade de nome
quando a variável em cada uma delas deve portar o mesmo nome. Entretanto,
a congeneridade ainda existe em casos em que a diferença é importante.
Primeiro, darei um exemplo trivial. Digamos que temos duas declarações:

int i;
int j;

Por precisão — na verdade, meramente para o código ser compilado! —


os nomes das variáveis, i e j, devem diferir um do outro. Há aqui uma conge-
neridade em atividade: se, por alguma razão, quiséssemos alterar o nome da
primeira variável para j, então também teríamos de alterar o segundo nome,
de j para outro nome. Dessa forma, as duas declarações não são independentes.
Tenho ouvido falar desse tipo de congeneridade como “congeneridade de
diferença” ou “congeneridade negativa”. Eu utilizo o nome mais curto, contra-
generidade (contranascence). Muito embora isso soe como o oposto de conge-
neridade, a contrageneridade é, na realidade, uma forma de congeneridade na
qual a diferença, preferentemente à igualdade, deve ser preservada.6
Um caso familiar de contrageneridade emerge em ambientes orientados
a objeto com herança múltipla (a habilidade de uma subclasse ser herdeira de
várias superclasses). Se a classe C é herdeira das classes A e B, então as ca-
racterísticas de A e B não deveriam ter os mesmos nomes: há uma contrage-
neridade de nome entre as características de A e as de B.
Como um exemplo mais concreto de contrageneridade, considere uma
aplicação em uma videolocadora.
A classe ItemDeProgramaDeLocação poderia ser herdeira de ItemDeInven-
tárioFísico e MeioDeGravação. Essas duas classes (ou suas superclasses) pode-
riam, individualmente, ter um atributo denominado extensão. Mas a extensão
do ItemDeInventárioFísico representaria talvez o comprimento físico de um item
em polegadas, e a extensão do MeioDeGravação representaria talvez a duração
de gravação de um programa (o filme ou o que estiver contido no videoteipe).
Muito embora você possa argumentar que duração poderia ser um nome
mais apropriado para o segundo atributo, você tem de apanhar o que está dis-
ponível na biblioteca de classes. Em nosso caso, a classe ItemDeProgramaDeLo-

6. O termo bombástico para ausência de congeneridade é disnascence: Dois elementos de soft-


ware são discongêneres se um deles não tiver absolutamente nada com o outro. De fato, in-
dependência também funciona muito bem!
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 225

cação precisa ser herdeira de ambos os atributos denominados extensão, e este


choque entre nomes causa um sério problema.
Ao vararmos uma biblioteca inteira de classes, das quais qualquer par
pode compartilhar uma subclasse, percebemos que há uma contrageneridade
de nomes através de todas as classes devido a esse risco de choques entre no-
mes sob uma herança múltipla. Não é de surpreender que a herança múltipla
adquiriu má reputação, e que as boas linguagens orientadas a objeto contêm
mecanismos que removem essa contrageneridade desenfreada.7

8.2.3 Congeneridade e fronteiras de encapsulamento


Avançarei mais: a congeneridade e a contrageneridade constituem a parte es-
sencial das construções modernas da engenharia de software.
Para explicar isso, gostaria de voltar ao tópico da seção 8.1, encapsula-
mento. Embora eu tenha definido encapsulamento e os níveis para os quais
ele pode aspirar, eu não relatei muito sobre o porquê da importância do en-
capsulamento.
O encapsulamento é um tipo de policiamento sobre a congeneridade, es-
pecialmente na contrageneridade. Imagine um sistema compreendendo
100.000 linhas de código. Pense, a seguir, que o sistema inteiro reside em um
único módulo; digamos um procedimento mestre. Imagine, em seguida, que
você tenha de desenvolver e manter esse sistema. A contrageneridade entre
as centenas de nomes variáveis seria, em seu próprio termo, um pesadelo: es-
colha simplesmente um nome para uma nova variável, e você vai ter de veri-
ficar dezenas de nomes diferentes para certificar-se de que não haverá
qualquer tipo de choque.
Outro problema seria a natureza enganosa do código. Considere duas li-
nhas de código que estejam adjacentes em uma listagem fonte. Você talvez
perguntasse: Por que as duas linhas são adjacentes? Seria porque elas devem
ser adjacentes (devido à congeneridade de posição) e porque a inserção de uma
nova linha iria pôr abaixo o sistema? Ou seria porque simplesmente coincidiu
de elas acabarem adjacentes quando o código foi todo colocado em grande
quantidade no mesmo módulo?
Assim, um sistema que não está fragmentado em unidades encapsuladas
apresenta dois problemas: uma congeneridade desenfreada (principalmente
por meio da contrageneridade), e a confusão sobre o que é realmente uma con-

7. Um dos melhores mecanismos de linguagem é a palavra-chave rename do Eiffel. Veja Meyer,


1992, como exemplo. Veja também o exercício 5 no Capítulo 12 para outra análise deste exem-
plo.
226 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

generidade absoluta e o que é uma adjacência ou similaridade acidental. (Um


exemplo de similaridade acidental seria o de duas variáveis denominadas i,
em duas classes inteiramente desconectadas e separadas. Não haveria qual-
quer congeneridade de nome neste caso; qualquer uma das variáveis poderia
ser renomeada como j sem conseqüências desastrosas.)
A congeneridade também é a razão de a orientação a objeto “funcionar”.
A orientação a objeto “elimina” — ou pelo menos “domestica” — uma parte da
congeneridade que “leva uma vida desregrada” em sistemas modulares con-
vencionais que somente tenham encapsulamento de nível-1. Novamente, ex-
plicarei com um exemplo: o exemplo da tabela de remodelagem (hash-table) da
seção 8.2.1.
Vou presumir um sistema que mantenha uma única tabela de remodela-
gem e que seja desenhado com um encapsulamento de nível-1 (o qual é o nível
de encapsulamento de, digamos, desenho estruturado). O sistema deve aces-
sar a tabela de remodelagem de diversos (ao menos dois) locais no código: lo-
cal(is) que atualiza(m) a tabela e local(is) que procura(m) símbolos na tabela.
O código nesses locais terá congeneridade de algoritmo; se você conceber um
melhor algoritmo de remodelagem, então terá de encontrar todos os locais de
código que utilizam o atual algoritmo e fazer as necessárias mudanças.
O encapsulamento de nível-1 nunca o encaminhará para esses locais e
nem lhe dirá quantos desses locais existem. Mesmo se existirem apenas dois
locais com o algoritmo de remodelagem, eles talvez estejam muito próximos ou
distantes na listagem do sistema. Você estará sozinho (a menos que consiga
alguma documentação acurada e amigável).
Um sistema orientado a objeto, com no mínimo encapsulamento de nível-
2, tem uma residência natural para o algoritmo de remodelagem na classe
única TabelaDeSímbolos. Embora ainda exista uma congeneridade de algorit-
mo entre a operação inserirSímbolo e a operação procurarSímbolo, a congeneri-
dade estará sob controle. Ela será encapsulada dentro da fronteira de um
único elemento (a classe TabelaDeSímbolos). Se uma boa orientação a objeto for
utilizada, em decorrência não haverá congeneridade por causa do algoritmo de
remodelagem em qualquer outro lugar do sistema (ou seja, em qualquer outro
lugar exceto TabelaDeSímbolos).

8.2.4 Congeneridade e manutenção


A congeneridade disponibiliza três diretrizes para o aperfeiçoamento da ma-
nutenção de um sistema:
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 227

1. Minimizar a congeneridade total — isso inclui, é certo, a contragenerida-


de — ao fragmentar o sistema em elementos encapsulados.
2. Minimizar qualquer congeneridade remanescente que cruze as fronteiras
de encapsulamento.
3. Maximizar a congeneridade dentro das fronteiras de encapsulamento.

As diretrizes anteriores transcendem a orientação a objeto. Elas se apli-


cam a qualquer abordagem de construção de software com encapsulamento de
nível-2 ou nível-3, ou até mesmo para um nível maior. Igualmente, como você
talvez tenha notado, as diretrizes expressam um princípio muito antiquado de
desenho: mantenha coisas semelhantes juntas e coisas diferentes separadas.
Entretanto, esse velho princípio nunca nos disse o que eram “coisas semelhan-
tes”; na verdade, trata-se de elementos de software com congeneridade mútua.
A Figura 8.2 exibe duas classes com congeneridade entre elas. (A conge-
neridade é direcional, com o sentido indicado pelas setas.) Uma parte dessa
congeneridade (mostrada pelas linhas mais encorpadas) viola os princípios da
orientação a objeto ao conectar o desenho interno de uma classe ao desenho
interno de outra classe: “coisas semelhantes” foram colocadas em estruturas
de software diferentes.

Figura 8.2 Linhas mostrando congeneridade (por exemplo,


congeneridade de nome), com as linhas mais encorpadas
violando as fronteiras de encapsulamento.

Por exemplo, uma linha de um método da Classe 1 que se refira a uma


variável dentro da Classe 2 viola o encapsulamento orientado a objeto. (Obser-
228 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

ve que a congeneridade direcional viola o encapsulamento somente quando ela


atravessa em direção ao interior de uma unidade encapsulada.)
A Figura 8.3 mostra duas outras classes isentas de qualquer tipo de vio-
lação de encapsulamento arruinando a congeneridade.

Figura 8.3 Linhas mostrando congeneridade, isentas de


violação de qualquer fronteira de encapsulamento.

A congeneridade representa um conjunto de interdependências no soft-


ware. A congeneridade explícita é aparente no código-fonte e pode freqüente-
mente ser descoberta por meio de uma busca simples, de um editor de texto
ou de uma listagem de remissão recíproca. A congeneridade implícita (tipo de
congeneridade dificilmente aparente no código em si) pode ser detectável so-
mente pela ingenuidade humana, auxiliada por toda e qualquer documenta-
ção existente para o sistema sob verificação.
A mente humana é uma ferramenta muito cara (e falível) para acompa-
nhar o curso do processo de difusão da congeneridade. A congeneridade implí-
cita, especialmente quando ela se espalha ao longo de fronteiras de
encapsulamento, apresenta um desafio a mais para os mantenedores de siste-
mas. A congeneridade implícita torna-se particularmente difícil de ser seguida
diversos meses após o código ter sido desenhado e escrito. Provavelmente, as
futuras ferramentas CASE assistirão as pessoas no tocante ao monitoramento
da congeneridade e quanto à revelação da congeneridade implícita. Isso será
especialmente proveitoso em grandes sistemas detentores de encapsulamento
de nível-2 (ou maior).
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 229

8.2.5 Abusos de congeneridade em sistemas orientados a


objeto
Conforme vimos na seção 8.2.3, as aptidões do encapsulamento de nível-2 da
orientação a objeto constituem um tremendo boon para um desenhista que es-
tiver tentando domesticar a congeneridade. Entretanto, nesta seção final so-
bre a congeneridade, darei três exemplos de como os desenhistas orientados a
objeto, eventualmente, violam o princípio de “manter a congeneridade em
casa” — ou melhor, no interior das fronteiras de classe. O primeiro diz respei-
to à função amigável da linguagem C++, o segundo à má utilização da herança
e o terceiro é relativo à introdução gratuita de congeneridade na transgressão
dos princípios orientados a objeto.
1. A função amigável da linguagem C++
A função amigável da linguagem C++ foi criada expressamente para vio-
lar fronteiras de encapsulamento. Ela constitui um elemento, do lado ex-
terno das fronteiras de uma classe, que tem acesso aos elementos
particulares dos objetos dessa classe. Assim, se uma função amigável ff
obtém o identificador de um objeto da classe C1, ela pode intrometer-se
nas partes internas desse indeciso objeto. Portanto, a congeneridade en-
tre ff e C1 é alta; ela inclui congeneridade de nome, tipo, convenção e as-
sim por diante. Qualquer mudança que um desenhista faça no desenho
interno de C1 pedirá que ff seja verificada de ponta a ponta, e possivel-
mente pedirá sua troca.
Se ff é amiga de uma única classe, então você pode argumentar que
ff é realmente parte dessa classe e que para todos os efeitos reside dentro
de sua fronteira. Bastante justo. Entretanto, se ff também é amiga de C2,
C3 e C4, então esse argumento falha. Infelizmente, muitos desenhistas
em C++ têm exatamente essa estrutura... e com amigos como esses, quem
precisa de inimigos!
Um uso autêntico da construção amigável, entretanto, funciona como
um patíbulo (scaffold) para inspeção dos estados internos dos objetos sob
teste. Uma vez que o encapsulamento de objetos orientados a objeto limi-
ta um exame mais minucioso de caixa branca (white-box) dos objetos, ele
ironicamente atrapalha a seqüência de testes de sistemas orientados a
objeto. A função amigável descobre o véu desse sigilo a partir da imple-
mentação da classe sob teste.
230 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2. Herança sem restrição


A construção de herança — ainda que extremamente comum na maioria
dos locais de trabalho orientados a objeto — pode, eventualmente, intro-
duzir uma violenta congeneridade. Se você deixar que uma classe se uti-
lize de elementos visíveis, de uma superclasse tanto externa quanto
internamente, será introduzida uma grande quantidade de congenerida-
de pelas fronteiras de encapsulamento (classes) mais importantes. Isso
incluirá a congeneridade de nome, congeneridade de classe e outras va-
riedades de congeneridade.
Em um local de trabalho no qual certa vez prestei consultoria, uma
equipe de trabalho de análise/desenho tinha iniciado a construção de
sua biblioteca de classes com algumas hierarquias de classe SmallTalk
muito bem planejadas. Por exemplo, RemessaDoméstica e RemessaDeEx-
portação eram ambas (em uma forma bem aceitável) subclasses de Re-
messa. Entretanto, a linguagem e a estratégia de desenho da equipe de
trabalho proporcionaram às subclasses acesso desenfreado a variáveis
de programação dentro das superclasses. Isso significava que os man-
tenedores das subclasses C1, C2 e assim por diante tinham de ficar
cientes — quase em uma base de minuto a minuto — de quaisquer mu-
danças no desenho interno da superclasse C, porque o mantenedor de
C poderia criar resultados desastrosos nas classes descendentes sim-
plesmente fazendo uma mudança inócua em um nome de variável (os-
tensivamente particular).
A equipe trabalhou em torno desse problema tendo Jim como respon-
sável por, digamos, RemessaDoméstica, RemessaDeExportação, Remessa e
outras classes afins. Jim certificou-se de que quaisquer mudanças que ele
fizesse em Remessa seriam propagadas para as classes congêneres de Re-
messa — e, de fato, havia muitas dessas classes. Infelizmente, entretanto,
a congeneridade desenfreada entre as classes não poderia ser repartida
aos membros individuais da equipe da mesma forma que as classes.
Como resultado, cada um dos mantenedores de classe tinha a tarefa te-
diosa de acompanhar a evolução das mudanças internas de desenho para
todas as superclasses daquela classe. Dessa forma, cada uma das mudan-
ças na biblioteca de classes causava uma grande ansiedade ao redor, visto
que as pressões de tempo não propiciavam à particular equipe qualquer
possibilidade de manter a documentação da biblioteca atualizada.
Se Jim e seus colegas tivessem levado em consideração a diretriz de
que a congeneridade não poderia cruzar as fronteiras do encapsulamento,
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 231

isso teria lhes informado que a herança por meio de uma subclasse deve-
ria apenas ficar restrita àquelas propriedades da superclasse já externa-
mente visíveis. (Outra forma de dizer isso seria: a noção do comporta-
mento abstrato herdado deveria ser divorciada da noção de herdar a im-
plementação interna desse comportamento.)8 Caso a biblioteca deles ti-
vesse sido desenhada de acordo com este princípio, suas vidas teriam sido
menos repletas de problemas, e talvez eles não tivessem chamado aquele
local de trabalho de “A Terra dos Acertos à Meia-Noite”.
3. Contando com acidentes de implementação
Em outro local de trabalho no passado, um programador chamado de “O
Astuto” (The Weasel) — não me pergunte o motivo! — tinha criado uma
classe Conjunto que fornecia o comportamento de um conjunto matemá-
tico. (Suas operações incluíam adicionar, remover, dimensionar e assim por
diante.) A classe aparentava estar bem desenhada e escrita e sempre tra-
balhava de forma satisfatória.
O Astuto utilizava essa classe Conjunto em diversos locais em suas
próprias aplicações. Por exemplo, utilizou a operação recuperar, que recu-
perava elementos do conjunto um por um em uma ordem randômica até
que todos os elementos fossem supridos. Todavia, O Astuto sabia que a
operação recuperar recuperava elementos exatamente na mesma ordem
em que eles tinham sido atrelados ao conjunto, embora isso não tivesse
sido documentado ou fosse esperado como uma propriedade inerente da
operação. Ele, por diversas vezes, se apossou desse fato acidental e não
documentado em sua aplicação. Ele tinha, dessa forma, criado uma con-
generidade de algoritmo por meio da fronteira de encapsulamento entre
a aplicação e as partes internas da operação recuperar.
Quando mais adiante Conjunto foi substituído por uma implementação
diferente — uma que aconteceu de não preservar a ordem da mesma for-
ma —, muitas das aplicações de O Astuto “explodiram”. Muitos dos usuá-
rios também “explodiram”, mas o Astuto não era encontrado em lugar
algum. Correram rumores de que ele tinha adotado outra identidade e fu-
gido clandestinamente para o Meio Oeste. Assim, vamos esperar que O
Dia do Astuto não se repita.

8. Veja Porter, 1992, para uma discussão posterior desse ponto.


232 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

8.2.6 O termo congeneridade


Embora eu não tenha cunhado a palavra congeneridade, — ela se encontra,
por exemplo, no Twentieth Century Dictionary do Chambers e no Third New
International Dictionary do Webster (como “connate”) —, tenho recebido um
bocado de fogo antiaéreo durante os últimos anos por utilizá-la. Conduzi um
pouco de pesquisa de mercado sobre acoplamento (coupling) como um termo
alternativo, mas em um local de trabalho os desenvolvedores se amontoaram
ao ouvir a palavra e um murmúrio surgiu entre eles. Então, voltando-se para
o meu lado, eles disseram como se fosse uma voz uníssona: “Ei, não faça com
que percamos nosso tempo com essa asneira estruturada antiquada!”.
Tentei interdependência em outro local de trabalho, mas aparentemente
esse termo era insosso demais para registrar um único sinal luminoso no ra-
dar cognitivo da equipe. Esse é o motivo porque gosto da palavra congeneri-
dade: trata-se de um termo que atrai a atenção das pessoas, e não há
necessidade de se contornar qualquer emprego prévio do mesmo.
Em certa ocasião, entretanto, eu me dei mal. Um desenvolvedor em de-
terminado local de trabalho disse: “Ei, tenho utilizado congeneridade durante
anos com um significado diferente. Eu digo que dois objetos são congêneres
quando eles são gerados juntos em run-time”. Já que eu mal conseguia discu-
tir essa utilização feita por ele do termo como representando “nascidos juntos”,
disse-lhe que ele poderia substituir o termo mais humilde, interdependência,
pela minha noção mais geral.9 Você pode fazer isso também, se assim o dese-
jar — “Eu, de qualquer maneira, não estou casado com a congeneridade”.

8.3 Resumo
O encapsulamento é um conceito venerável no mundo do software. A sub-ro-
tina, inventada nos anos 40, introduziu o encapsulamento de código nos mó-
dulos procedurais; isso corresponde ao encapsulamento de nível-1. Entretanto,
as estruturas orientadas a objeto são mais sofisticadas do que as estruturas
procedurais convencionais, como a sub-rotina. A orientação a objeto envolve
pelo menos o encapsulamento de nível-2. No encapsulamento de nível-2, as
operações (implementadas pelos métodos) são por si próprias encapsuladas,
juntamente com os atributos (implementados pelas variáveis), em classes.

9. Se dois objetos tinham de ser gerados conjuntamente, então diria que eles tinham congeneri-
dade de geração. Se dois objetos tinham de ter a mesma existência, eu diria que eles tinham
congeneridade de existência — ou, se quisesse impressionar as pessoas, congeneridade de du-
ração.
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 233

As complexidades do encapsulamento de nível-2 introduzem interdepen-


dências insólitas entre os elementos de desenho. Em vez de conceder a cada
espécie de interdependência o seu próprio termo, introduzo o termo geral con-
generidade. A congeneridade existe quando dois elementos de software devem
ser alterados conjuntamente em algum tipo de circunstância a fim de preser-
var a exatidão do software. A contrageneridade é uma forma de congeneridade
na qual a diferença, preferentemente à similaridade, deve ser preservada. A
não-congeneridade (disnascence) é a ausência de congeneridade.
A congeneridade existe sob diversas formas. A congeneridade estática de-
riva da estrutura léxica de uma listagem de código. Exemplos incluem a con-
generidade de classe e a congeneridade de convenção. A congeneridade
dinâmica depende do padrão de execução do código em run-time. Exemplos in-
cluem a congeneridade temporal e a congeneridade de valor. A congeneridade
explícita é imediatamente aparente a partir da leitura de uma listagem de có-
digo. Um exemplo é a congeneridade de nome. A congeneridade implícita é
aparente só a partir do estudo de um código ou da documentação que o acom-
panha. A congeneridade de execução ou algoritmo normalmente é implícita.
Uma abundante congeneridade implícita eleva os custos de manutenção de
software.
O encapsulamento de nível-2 da orientação a objeto trata o problema da
congeneridade, que fica potencialmente descontrolada em sistemas grandes e
modernos. Esse encapsulamento proporciona estruturas de classe sólidas em
cujas fronteiras esse descontrole de congeneridade pode ficar cercado.
Entretanto, há diversos meios pelos quais a congeneridade pode escapar
das fronteiras do encapsulamento — até mesmo em um desenho orientado a
objeto. Neste capítulo, vimos três exemplos de desenhos pobres: o primeiro foi
o uso da função amigável na linguagem C++, de forma deliberada para anular
os benefícios do encapsulamento orientado a objeto; o segundo foi o uso mal
orientado da herança para permitir que uma subclasse seja herdeira da im-
plementação de uma superclasse; o terceiro foi capacitar aos detalhes internos
(e provavelmente voláteis) do algoritmo de uma classe para que contassem
com o código em outras classes.
Os três exemplos anterior violam o princípio central do desenho orientado
a objeto: minimizar a congeneridade total — isso inclui, é certo, a contragene-
ridade — pela fragmentação do sistema em elementos encapsulados; a seguir,
minimiza qualquer congeneridade remanescente que atravesse as fronteiras
de encapsulamento pela maximização da congeneridade dentro dessas mes-
mas fronteiras.
234 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

8.4 Exercícios
1. Um homem em um bar me disse, em uma ocasião, que toda idéia sobre
música moderna poderia ser encontrada em algum lugar nas obras de
Haydn. Poderia ser dito algo semelhante sobre o livro de Yourdon e Cons-
tantine com relação ao desenho estruturado (Yourdon e Constantine,
1979): suas páginas formam uma obra-prima de idéias de desenho muitas
vezes esquecidas, que são “redescobertas” de tempos em tempos. Verifi-
que esse livro para ver se Yourdon e Constantine têm algo a dizer sobre
congeneridade.
2. A utilização da declaração goto ficou famosa durante as últimas décadas
como uma das causas de termos software incompreensível. Do ponto de
vista da congeneridade, você consegue justificar a má reputação de goto?
3. Suponha que você tenha ficado cansado da orientação a objeto. Você de-
seja criar um novo paradigma relacionado a software que emprega níveis
de encapsulamento e tem várias formas de congeneridade. Explique (de
forma geral) como você estabeleceria os critérios e as diretrizes de dese-
nho para a congeneridade e o encapsulamento em seu paradigma.
4. Este capítulo discutiu a congeneridade principalmente em termos de có-
digo de programação. Há quaisquer outros exemplos de congeneridade
que emergem no contexto mais amplo do projeto global de desenvolvi-
mento de software?
5. Na seção 8.2.4, sugeri que a congeneridade implícita que cruza as fron-
teiras de encapsulamento normalmente provoca problemas particular-
mente a mantenedores de sistemas orientados a objeto. Você pode dar
exemplos dessa espécie de congeneridade e sugerir como torná-la mais
explícita, e, por conseguinte, mais fácil de ser rastreada?
6. É necessário que se faça pesquisa adicional sobre as formas de congene-
ridade aplicáveis a paradigmas modernos de desenho de software, espe-
cialmente à medida que o desenho orientado a objeto vai se tornando
cada vez mais popular. Realize uma experiência para trazer à tona os
graus de danos provocados pelas diferentes variedades de congeneri-
dade (por exemplo, congeneridade de nome e posição). O experimento po-
deria medir os efeitos da congeneridade (ambos dentro e através das
fronteiras de encapsulamento) em fatores dependentes pressupostos, tais
como tempo/custo da compreensão humana, tempo/custo de depuramento
e tempo/custo de modificações. Talvez você possa recrutar voluntários
Cap. 8 ENCAPSULAMENTO E CONGENERIDADE 235

para rever o código-fonte e medir seus tempos para detectar bugs delibe-
radamente semeados no mesmo.

8.5 Respostas
1. Sim, Yourdon e Constantine começam uma exploração do que é essencial-
mente congeneridade no Capítulo 3 do livro deles. Entretanto, após intro-
duzirem esse conceito (sob o termo vago estrutura) e abordá-lo
brevemente, o capítulo se desvia de certo ângulo do conceito geral. De-
pois, especialmente no Capítulo 6, o livro flerta novamente, e de forma
atormentadora, com esse tópico.
2. Há muito tempo sabemos que o uso indisciplinado da declaração goto
provoca a divergência das estruturas dinâmicas e estáticas do código. Em
termos de congeneridade, isso implica que a congeneridade de posição (na
listagem de código) forneça pouca pista para a congeneridade dinâmica
de execução (em run-time). Visto que os mantenedores fazem modifica-
ções no código estático, os gotos aumentam o risco de uma alteração es-
tática violar alguma congeneridade de execução. Além do mais, qualquer
goto induz uma congeneridade de nome extra entre o goto em si e a eti-
queta que é o alvo de goto, juntamente com a contrageneridade entre os
próprios nomes de etiquetas.
3. Nesse caso, veja uma estrutura possível sobre a qual se baseia uma defi-
nição de um futuro paradigma de software:
a. Defina o objetivo pretendido e o alcance de aplicabilidade de seu pa-
radigma.
b. Defina a estrutura de encapsulamento do paradigma. Estabeleça
quais são os componentes do paradigma e quais componentes estão
contidos dentro de quais outros componentes.
c. Em termos da estrutura de encapsulamento anterior, estabeleça as
regras padrão de visibilidade do paradigma. Isso prescreverá as cone-
xões permitidas entre componentes e especificará as “fronteiras de
privacidade” estabelecidas pela estrutura de encapsulamento.
d. Liste as possíveis formas de congeneridade inerentes no paradigma.
Haverá congeneridade explícita, a qual aparece no código-fonte, e con-
generidade implícita, que será mais difícil de ser percebida porque é
“invisível”. Como vimos, a congeneridade implícita torna-se particu-
larmente sutil quando ela transcende a estrutura de encapsulamento
oficial do paradigma.
236 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

e. Classifique tanto quanto possível os efeitos perniciosos de cada forma


de congeneridade em vários contextos.
f. Sugira a heurística (ramo da ciência histórica que consiste na pesqui-
sa de documentos do passado) para derivar ou modificar software de-
senhado segundo esse paradigma a fim de minimizar os efeitos
perniciosos já mencionados.
4. Sim, há muitos exemplos de congeneridade que transpõem resultados de
projeto. Por exemplo, você pode encontrar congeneridade entre o modelo
de requisitos de usuário e o modelo de desenho de uma implementação
de software desses requisitos. Recentemente, achei uma congeneridade
de nome infortunadamente vasto quando um negócio decidiu alterar a
palavra Freguês para Cliente. A destruição que isso causou foi imensa!
Uma boa ferramenta de modelagem, “de ciclo de vida integral de projeto”,
poderia seguir os passos dessas centenas de linhas de congeneridade por
meio de resultados e, com isso, reduzir a carga sobre a mente humana
pelo fato de ela ficar ausente na realização desse processo.
5. Recentemente, vi dois exemplos de congeneridade implícita em dois sis-
temas distintos orientados a objeto.
No primeiro exemplo, um par de classes em um sistema de negócio con-
tinha individualmente o número 5 (representando o número de prédios comer-
ciais tidos pela companhia). Isso criava uma congeneridade implícita de valor
entre as duas classes, porque alterar um 5 (mas não o outro) para 6 provocaria
um erro. Se a constante literal 5 fosse de outra forma denotada por número-
DeEscritórios, conseqüentemente a congeneridade ficaria explícita e se teria
um problema a menos: altere o valor uma vez, e ele será alterado em todos os
lugares. Para fazer isso, você presumivelmente armazenaria o valor de núme-
roDeEscritórios em um banco de dados.
No segundo exemplo, uma aplicação voltada a comunicações em tempo
real continha dois objetos que transmitiriam um enorme volume de comuni-
cações por meio de uma rede exatamente ao mesmo tempo. Isso fez com que
a rede ficasse atravancada desnecessariamente porque não havia qualquer ra-
zão para que os objetos tivessem de iniciar essa comunicação simultaneamen-
te. O problema foi solucionado quando os dois objetos foram forçados a
alternar suas transmissões. Em outras palavras, esse sistema tinha uma con-
generidade temporal, explicitada pelo estabelecimento de uma tabela de esca-
lonamento para a comunicação dos objetos. (Note que, neste exemplo, se fez
necessário a preservação da contrageneridade de tempo para o desempenho,
mais do que para a estrita perfeição do software.)
D omínios, grau de dependência
e coesão
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO
9.Domínios, Grau de Dependência e Coesão

A s classes constituintes de um sistema não são todas idênticas. Por exem-


plo, em uma aplicação de corretagem, você poderá encontrar as classes Pa-
trimônio, PosiçãoDeConta, Data, Hora, Lista e Conjunto. Em um sistema de
aplicação da eletrônica na aviação, poderão ser encontradas as classes Flape,
TanqueDeCombustível, Data, Hora, Conjunto e Árvore.
Note que há algo referente às classes Patrimônio e TanqueDeCombustível
que as distingue em relação às classes Data e Conjunto. Por exemplo, a classe
Patrimônio aparenta ser mais complexa, intrincada e especializada do que a
classe simples Data. Isso porque Patrimônio e TanqueDeCombustível são do do-
mínio de negócio, enquanto Data e Conjunto são do domínio de base.1 Além do
mais, as classes Patrimônio e TanqueDeCombustível parecem ser, ainda, dife-
rentes uma da outra — porque elas são provenientes de duas indústrias dife-
rentes (corretagem e aviação).
Inicio este capítulo definindo domínios de classe. Na segunda seção do ca-
pítulo, introduzo o conceito de grau de dependência como uma medida quan-
titativa da “sofisticação” de uma classe e revelo como as classes de domínios
mais altos normalmente possuem maiores valores de grau de dependência. Na
terceira seção do capítulo, defino uma medida qualitativa de uma classe, a
coesão de classe, característica essa cujas recomendações para a “excelência de
uma classe” é que uma classe deverá ser baseada em um único domínio.

1. Note que, nesse caso, refiro-me a negócio no sentido mais amplo possível. Incluo aí negócios
que envolvem eletrônica aplicada à aviação, instrumentação, controle de fornos microondas,
juntamente com os negócios mais tradicionais de atividades bancárias, seguros e atividades
pesqueiras.

237
238 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

9.1 Domínios de Classe de Objetos


Um sistema orientado a objeto normal conterá classes dos quatro domínios
mais importantes: o domínio de aplicação, o domínio de negócio, o domínio de
arquitetura e o domínio de base. Cada um desses domínios tem diversos gru-
pos de classes dentro dele:

• O domínio de aplicação — que compreende as classes de valor para


uma aplicação
Classes gerenciadoras de eventos
Classes reconhecedoras de eventos
• O domínio de negócio — que compreende as classes de valor para uma
indústria ou companhia
Classes de relacionamento
Classes de papel
Classes de atributo
• O domínio de arquitetura — que compreende as classes de valor para
uma arquitetura de implementação
Classes de interface humana
Classes de manipulação de banco de dados
Classes de comunicação de máquina
• O domínio de base — que compreende as classes de valor para todos
negócios e arquiteturas
Classes semânticas
Classes estruturais
Classes fundamentais
Nas próximas seções, explico, por meio de exemplos, o significado de cada
um dos domínios, e começo com as classes mais simples — as presentes na
parte inferior da relação anterior.

9.1.1 O domínio de base


As classes no domínio de base são utilizáveis em várias aplicações de muitas
indústrias diferentes e em uma ampla faixa de arquiteturas de computador.
Em outras palavras, o domínio de base compreende as classes detentoras da
reutilização mais ampla possível.
O domínio de base tem três grupos de classes: fundamentais, estruturais
e semânticas. Veja aqui alguns exemplos de classes de cada grupo:
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 239

• As classes fundamentais incluem NúmeroInteiro, Booleano e Char. Es-


sas classes são tão básicas que muitas linguagens orientadas a objeto
chegam a incluí-las como tipos de dados convencionais, integrantes e
simples.
• As classes estruturais implementam estruturas. Elas interpretam da
mesma forma que o currículo de um curso universitário Estrutura de
Dados 101, incluindo Pilha, Fila, Lista, ÁrvoreBinária, Conjunto e assim
por diante. Também conhecidas como classes recipientes (contêiner
classes), elas são freqüentemente designadas por meio da generalida-
de.
• As classes semânticas incluem Data, Hora, Ângulo, Dinheiro e Massa.
(Algumas pessoas incluem nesse grupo de classes formatos geométri-
cos básicos, tais como Ponto, Linha, Polígono e Círculo.) As classes se-
mânticas possuem um significado mais rico que as classes simples
NúmeroInteiro ou Char. Além disso, seus valores de atributos podem
ser expressos em unidades, tais como horas, dólares, metros ou barris
por quinzena.

As classes no domínio de base, por definição, provam ser úteis em qual-


quer aplicação, em qualquer negócio e em qualquer parte. Uma classe Lista ou
Data provavelmente aparecerá em um sistema médico bem como em um sis-
tema de videoteca.
Uma classe do domínio de base pode ser construída em outras classes
desse mesmo domínio. Por exemplo, a classe Ângulo pode utilizar NúmeroReal
em sua implementação, e Polígono pode utilizar Conjunto. A forma como orde-
nei os domínios de classes não foi acidental; as classes mais baixas da lista
tendem a ser utilizadas pelas classes mais altas, como veremos novamente
quando examinarmos a coesão de classe na seção 9.3.

9.1.2 O domínio de arquitetura


As classes no domínio de arquitetura são utilizáveis em várias aplicações de
muitas indústrias diferentes. Entretanto, a reutilização de classes arquitetu-
rais é limitada a uma única arquitetura de computador.
O domínio de arquitetura tem três grupos de classes: classes de comuni-
cação de máquina, de manipulação de banco de dados e de interface humana.
Veja aqui alguns exemplos de classes de cada grupo:
240 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• As classes de comunicação de máquina incluem Porta e MáquinaRemota.


• As classes de manipulação de banco de dados incluem Negociação e
Backup.
• As classes de interface humana incluem Janela e BotãoDeComando.

As classes arquiteturais são úteis em qualquer aplicação e em qualquer


negócio, contanto que a implementação seja realizada em uma arquitetura fí-
sica suportada por essas classes. Em outras palavras, não há qualquer possi-
bilidade de existir uma única biblioteca de classes do domínio de arquitetura
para o mundo todo, porque as classes, como Porta ou Backup, terão de existir
em diversas versões para as várias arquiteturas de computador existentes.
Assim, sua escolha da biblioteca de classes do domínio de arquitetura depen-
derá da(s) arquitetura(s) de hardware e software utilizada(s) em seu local de
trabalho.

9.1.3 O domínio de negócio


As classes no domínio de negócio são úteis em muitas aplicações, desde que
restritas a uma particular indústria, tal como no ramo bancário, na medicina
ou na eletrônica aplicada à aviação.
O domínio de negócio tem três grupos de classes: classes de atributo, pa-
pel e relacionamento. Veja aqui alguns exemplos de classes de cada grupo:

• As classes de atributo abrangem as propriedades dos itens no mundo


dos negócios. Exemplos são Saldo (de uma conta bancária) ou Tempe-
raturaDeCorpo (de um paciente). Embora essas classes sejam obvia-
mente similares a Dinheiro e Temperatura (que são classes
fundamentais semânticas), elas não são idênticas. O saldo de uma
conta bancária e a temperatura do corpo de um paciente serão sub-
metidos a certas regras de negócio que não se aplicam às classes fun-
damentais mais genéricas. Por exemplo, o valor de um saldo de conta
pode ficar restrito a situar-se dentro de certos limites. A transgressão
desses limites pode sinalizar um erro ou disparar alguma outra ativi-
dade de negócio.
• As classes de papel derivam de “papéis que as coisas desempenham”
nos negócios.2 Exemplos delas são Cliente e Paciente. Quando você

2. Um papel em uma análise orientada a objeto é análogo ao tipo de entidade em modelagem


de informações.
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 241

analisa o domínio de negócio, essas classes provavelmente são as pri-


meiras classes, e as mais óbvias, a serem identificadas.
• As classes de relacionamento derivam de associações entre coisas no
mundo dos negócios. Elas incluem TitularidadeDeConta (por um cliente
de banco) e SupervisãoDePaciente (por uma enfermeira).

Uma parte da aplicabilidade das classes de negócio pode até mesmo ser
mais delimitada do que o bloco representativo de uma indústria: elas talvez
sejam proveitosas só para uma única corporação. De fato, visto que algumas
grandes corporações se envolvem em diversos segmentos de negócios, a utili-
zação de uma dada classe de negócio talvez nem encerre mais que uma divi-
são. Por exemplo, tenho conhecimento de uma empresa que está
desenvolvendo sete classes diferentes, todas nomeadas como OrdemDeCompra
— e essa empresa tem só nove divisões! Suspeito que eles poderiam efetuar
uma reengenharia na empresa para reduzir o número de classes OrdemDe-
Compra incompatíveis. Mas duvido que, algum dia, eles conseguissem reduzir
esse número para uma única classe, porque até mesmo duas divisões não
prosseguiriam fazendo compras exatamente nos mesmos moldes.
Novamente, vemos o modelo em que as classes nos domínios mais altos
são construídas, com base nas classes mais baixas. Por exemplo, Titularidade-
DeConta vai referenciar as classes Cliente e Conta, e, no mínimo, um atributo
de Conta pertencerá à classe Saldo.

9.1.4 O domínio de aplicação


Uma classe no domínio de aplicação é somente utilizada dentro de uma única
aplicação (ou de um número pequeno de aplicações afins).
O domínio de aplicação contém dois grupos de classes: classes reconhece-
doras de eventos (detecção da ocorrência de um particular evento) e classes ge-
renciadoras de eventos (execução do apropriado plano de ação de negócio
destinado a esse particular evento). Veja aqui alguns exemplos de classes de
cada grupo:

• As classes reconhecedoras de eventos são construções de software que


monitoram a entrada de dados para verificar a ocorrência de especí-
ficos eventos no meio. Um exemplo é um objeto da classe MonitorDe-
TemperaturaDePaciente o qual procura, entre outros, pelos eventos
paciente desenvolve febre (“ele fica muito quente”) e paciente torna-se
hipotérmico (“ele fica muito frio”).
242 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• As classes gerenciadoras de eventos executam o plano de ação de ne-


gócio mais correto na ocorrência de um evento de um dado tipo. Um
exemplo típico de gerenciador de eventos é o de um objeto da classe
AquecimentoDePacienteHipotérmico, que imediatamente envia mensa-
gens para outros objetos no intuito de aumentar a temperatura do pa-
ciente e solicitar cuidados médicos, anunciando por meio de um
alarme situado em uma unidade de enfermaria. (O objeto Aquecimen-
toDePacienteHipotérmico, naturalmente, apenas ficaria ativo no evento
em que um paciente se tornasse hipotérmico.) Outro exemplo, extraí-
do de uma diferente aplicação hospitalar, é o de RelaçãoDePacientesPa-
raCirurgia. Um exemplo em aviação de uma classe gerenciadora de
eventos é PosicionarFlapeEmConfiguraçãoDeAterrissagem.

Uma classe no domínio de aplicação tem uma reutilização muita restrita.


Na realidade, a maioria das classes nesse domínio é relevante somente em re-
lação a uma aplicação e, assim, não apresenta em absoluto qualquer reutili-
zação. Por exemplo, a classe RelaçãoDePacientesParaCirurgia provavelmente
somente teria utilidade para o Sistema de Gerenciamento de Cirurgias.

9.1.5 A origem das classes em cada domínio


Para a maioria das pessoas, a habilidade de formar estoques de bibliotecas
com software reutilizável é a condição indispensável da orientação a objeto, e
muitos locais de trabalho certamente dedicam grande parte de seus esforços
de desenho à reutilização apurada das classes neles construídas. Mas, como
vimos nas seções precedentes, as classes em diferentes domínios apresentam
graus diferentes de reutilização. As classes no domínio mais baixo têm a maior
reutilização, ao passo que as classes no domínio mais alto têm a menor reuti-
lização — conforme ilustrado pela Figura 9.1.
Imagino que esse seja um argumento evasivo. Afinal de contas, defini o
domínio de base como o domínio de classes com reutilização universal e o do-
mínio de aplicação como o das estruturas de software somente úteis para uma
única aplicação. Todavia, a questão da reutilização é importante quando ela
se depara com a famosa e velha questão: de onde vêm as classes? A resposta
depende muito do domínio da classe em questão.
Para as classes de base, a resposta é simples: você as compra de um dis-
tribuidor de classes. O desenvolvimento de uma biblioteca de classes de base
é um empreendimento difícil e custoso. Não tente fazê-lo em casa. Na realida-
de, caso você realmente o faça, e se conseguir terminar esse trabalho, terá to-
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 243

das as classes de que necessita — mas também você gastará mil vezes mais
do que a biblioteca lhe custaria.
Você certamente terá de adicionar a sua biblioteca classes de base adqui-
ridas, com algumas classes de sua própria elaboração. Até aí tudo bem! Po-
rém, compre, tome emprestado e peça quaisquer classes de base de que você
precise antes de recorrer à opção última de construí-las.

Figura 9.1 Domínios de classes e suas reutilizações.

A origem das classes no domínio de arquitetura é similar àquela do do-


mínio de base, mas existem quatro diferenças:

• Talvez você tenha de adquirir as classes de arquitetura do(s) distri-


buidor(es) da infra-estrutura de seu hardware e software (muito em-
bora outras empresas distribuidoras forneçam bibliotecas para
arquiteturas populares).
• Talvez você tenha de passar por incompatibilidades que lhe trarão
aborrecimentos, pelas porções da biblioteca de classes de arquitetura
provenientes de diversos distribuidores.
• Seu(s) distribuidor(es) de biblioteca de classes de arquitetura talvez
tenha(m) edificado os produtos sobre uma biblioteca de classes de
base distinta da sua.
• Sua biblioteca de classes de arquitetura adquirida provavelmente ne-
cessitará de mais modificação, construção customizada e personaliza-
244 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

ção do que sua biblioteca de classes de base em razão dos dois pontos
anteriores.

As classes no domínio de negócio são, especialmente, interessantes — e


desafiadoras. Essas não são classes normalmente possíveis de se comprar nes-
te exato momento devido a, pelo menos, duas razões. Em primeiro lugar, os
distribuidores gerais não têm a habilidade industrial para desenvolver classes
para hospitais, bancos, aviação ou telecomunicações. Em segundo lugar, o
mercado para uma biblioteca de classes como essa seria um pesadelo. Todo e
qualquer comprador desejaria produtos personalizados: “Nossa companhia or-
gulha-se por ser especial e diferente. A classe Produto que você nos vendeu
não atende a nossas necessidades de negócios”. Entretanto, mesmo que eu te-
nha escrito isso, a situação está mudando. Nós agora estamos começando a ver
classes do domínio de negócio e componentes (veja o Capítulo 15) para algu-
mas indústrias (tais como, de fato, atividades bancárias e telecomunicações)
sendo oferecidas à venda.
Mesmo assim, uma vez que demorará certo tempo até que o software do
domínio de negócio se torne acessível em grande escala, hoje em dia você, mui-
to certamente, terá de desenvolver as classes no domínio de negócio por seus
próprios esforços. E você terá de tomar um enorme cuidado na análise de seus
requisitos — utilizando técnicas de modelagem de relacionamento de papel ou
modelagem de relacionamento de entidade — porque eles se tornarão uma re-
serva preciosa da perspicácia dos negócios de sua companhia. Igualmente,
você terá de tomar um grande cuidado em seu desenho, porque essas classes
terão uma significativa reutilização se elas forem bem analisadas e desenha-
das — porém, não há virtualmente nenhuma reutilização se as coisas não
ocorrerem dessa forma.
A competitividade de sua companhia talvez dependa do período para im-
plementação de novos sistemas de software, fato esse que estará significativa-
mente subordinado à qualidade das classes de negócio existentes na biblioteca
de seu local de trabalho. Assim, embora você consiga um bocado de lucro pro-
veniente de suas bibliotecas de base e arquitetural, o sucesso estratégico da
orientação a objeto em sua empresa vai eventualmente depender do quão bem
você construa sua biblioteca de classes do domínio de negócio para consumo
local.3
O domínio no posto mais alto é o domínio de aplicação. Não se preocupe
demais, neste caso, em desenhar para obter reutilização; não importa que o

3. Volto ao tópico de classes de negócio em bibliotecas adquiridas no exercício 1, no final deste


capítulo.
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 245

seu desenho fique tão bom, você não conseguirá muita reutilização. A princi-
pal origem para as classes nesse domínio são os eventos de negócio que você
descobre durante a análise.
Talvez “construção” seja um termo melhor que “classe” nos parágrafos
anteriores, visto que as classes reconhecedoras e gerenciadoras de eventos po-
dem ser implementadas da mesma forma que os procedimentos convencionais,
ou pacotes de utilidade de instância simples, em lugar de classes de raça de
puro sangue. Por exemplo, o objeto gerenciador de eventos AquecimentoDePa-
cienteHipotérmico poderia vir a ser um procedimento impar. Alternativamente,
ele poderia tornar-se a única operação de uma nova classe, ou mesmo poderia
tornar-se uma operação da classe Paciente já existente.

9.2 Grau de dependência


A seção 9.1 tratou os domínios de classe de forma qualitativa. Nessa seção,
ofereço uma forma quantitativa para dizer quão distante do domínio de base
uma classe se situa. A medida é chamada de grau de dependência.

9.2.1 O que é grau de dependência?


Eu achei que você nunca perguntaria uma coisa dessas! O grau de dependên-
cia mede a maquinaria auxiliar total de uma classe. A “maquinaria auxiliar
total” compreende todas as classes nas quais a referida classe se baseia a fim
de operar. Em outras palavras, caso você conte todas as classes referenciadas
por uma classe C, e, em seguida, conte as classes as quais elas se referem, e
assim por diante, o número total será o grau de dependência de C.
A fim de definir o “grau de dependência” formalmente, eu primeiro defino
os termos “conjunto classe-referência direto” e “conjunto classe-referência in-
direto”. (Observação: os próximos parágrafos parecem mais difíceis do que
realmente são. Queira permanecer sintonizado, porque tenho de definir “grau
de dependência” formalmente apenas uma vez, e então tudo estará acabado.
Se você quiser tirar proveito do efeito de anestésicos, siga em frente!)

O conjunto classe-referência direto de uma classe C é o conjunto de


classes para as quais a classe C referencia diretamente.

Na maioria das linguagens orientadas a objeto, uma classe C pode refe-


renciar diretamente outra classe, D, sob qualquer uma das seguintes formas:
246 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• C é herdeira de D.
• C tem um atributo da classe D.
• C tem uma operação com um argumento de entrada de dados da clas-
se D.
• C tem uma variável da classe.
• C tem um método que envia uma mensagem com um argumento re-
tornado da classe D.
• C tem um método contendo uma variável local da classe D.
• C supre D como um parâmetro de classe real em uma classe parame-
trizada.
• C tem uma classe amiga (friend) D (em C++).

Digamos que o conjunto classe-referência direto de C compreenda as


classes C1, C2,..., Cn. Por conseguinte, o conjunto classe-referência in-
direto de C é a união do conjunto classe-referência direto de C e os
conjuntos classe-referência indiretos de C1, C2,..., Cn.4

Esta definição obviamente se repete. Eu posso parar essa repetição dizen-


do que os conjuntos classe-referência — tanto diretos quanto indiretos — de
cada classe (incluindo NúmeroInteiro, NúmeroReal, Booleano) no domínio de
base estão vazios. (O que você escolhe como seu domínio de base é arbitrário;
não importa quais classes você colete contanto que se aferre às mesmas e de-
fina seus conjuntos classe-referência como vazios.)
E agora — finalmente — veja aqui a definição de grau de dependência:

O grau de dependência direto de uma classe é o tamanho de seu con-


junto classe-referência direto. O grau de dependência indireto de uma
classe é o tamanho de seu conjunto classe-referência indireto.5

4. Se você preferir uma definição até mesmo mais matemática: o conjunto classe-referência in-
direto de C é equivalente ao fechamento transitivo no conjunto classe-referência direto de C.
5. Alguns autores utilizam o termo acoplamento de classe total (total class coupling) para o ta-
manho de um conjunto de referência direto de uma classe.
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 247

Nesse ponto, muito provavelmente você está se descabelando todo. Por


sorte, entretanto, existe um modo muito mais claro e intuitivo de examinar o
conceito de grau de dependência. Vamos utilizar uma seta para exibir uma re-
ferência direta de classe. No exemplo mostrado na Figura 9.2, o conjunto clas-
se-referência direto de C é C1, C2 e C3. Assim, o grau de dependência direto de
C é simplesmente 3.

Figura 9.2 C e seu conjunto classe-referência direto.

A Figura 9.3 mostra o conjunto classe-referência indireto de C. Você en-


contra o grau de dependência indireto de C contando todas as classes nesse
diagrama, ou seja, todas as classes na árvore cuja raiz é C e cujas folhas são
as classes fundamentais na parte inferior (denotadas por F1 e assim por dian-
te). O grau de dependência indireto de C portanto é 12. (Observe que incluí as
classes do conjunto classe-referência direto em minha contagem do conjunto
classe-referência indireto.)

Figura 9.3 C e seu conjunto classe-referência indireto.


248 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

A Figura 9.4 mostra um exemplo concreto de grau de dependência, no


qual o grau de dependência indireto da classe Retângulo é 4, visto que ela tem
as quatro classes: Ponto, Comprimento, NúmeroReal e Booleano em seu conjun-
to de referência indireto.6

Figura 9.4 Retângulo e seu conjunto classe-referência indireto, com a


marcação do grau de dependência indireto para cada classe.

Ainda que a classe NúmeroReal se refira ao Booleano (em uma operação


de comparação) e NúmeroInteiro (em uma operação de arredondamento), ado-
tei a convenção normal “zero” para especificar o grau de dependência de Nú-
meroReal (e seus companheiros fundamentais) como zero.

9.2.2 A utilização do grau de dependência


O grau de dependência fornece-nos uma medida da sofisticação da classe, —
ou seja, quão alta a classe se encontra acima do domínio de base. Em conse-
qüência, as classes nos domínios mais altos têm um alto grau de dependência
indireto e aquelas nos domínios mais baixos têm um baixo grau de dependên-
cia indireto.

6. Um desenho diferente para Retângulo lhe proporcionaria um grau de dependência levemente


diferente.
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 249

Um grau de dependência indireto inesperado pode indicar uma deficiên-


cia no desenho de classe. Por exemplo, se você encontrar uma classe com um
alto grau de dependência indireto situada em um domínio baixo, então poderá
haver um problema com a coesão da classe. (Examino a coesão de classe na
seção 9.3.) Alternativamente, se uma classe em um alto domínio tem um baixo
grau de dependência indireto, então provavelmente ela foi desenhada do nada,
ou seja, o desenhista construiu-a diretamente de NúmeroInteiro, Char e outras
classes fundamentais, em lugar de reutilizar classes intermediárias da biblio-
teca.

9.2.3 A Lei de Deméter


Lieberherr e Holland fazem menção à Lei de Deméter como um princípio
orientador para limitar o grau de dependência direto de uma classe ao limitar
o tamanho de seu conjunto classe-referência direto.7 (Entretanto, os autores
na verdade não empregam os termos “grau de dependência” e “conjunto clas-
se-referência direto”.) Uma expressão geral da Lei de Deméter é a seguinte:

Para um objeto obj da classe C, e para qualquer operação op defini-


da para obj, cada objeto destinatário de uma mensagem dentro da
implementação de op deve ser um dos seguintes objetos:

1. O próprio objeto obj — especificamente, self e super (em


Smalltalk), this (em C++ e Java) ou Current (em Eiffel).

2. Um objeto referenciado por um argumento dentro da assinatura


de op.

3. Um objeto referenciado por uma variável de obj (incluindo


qualquer objeto dentro das coleções referenciadas por obj).

4. Um objeto criado por op.

5. Um objeto referenciado por uma variável global.

Há duas versões da lei, diferindo somente em suas interpretações do pon-


to 3. A Lei Forte de Deméter define uma variável como apenas uma variável

7. Na mitologia grega, Deméter era a deusa da colheita. Entretanto, essa Lei de Deméter tem
seu nome originário de um projeto orientado a objeto, denominado Deméter. Veja Lieberherr
e Holland, 1989, para detalhes adicionais.
250 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

definida na classe C em si. A Lei Fraca de Deméter define uma variável como
quer uma variável de C ou uma variável que C herda de suas superclasses.
A Lei de Deméter é eminentemente razoável, pois ela restringe referên-
cias arbitrárias a outras classes dentro de uma dada classe. Segundo a lei, o
código dentro de C irá referir-se apenas ao número mínimo viável de outras
classes. Prefiro a Lei Forte à Lei Fraca de Deméter pelo fato de que ela ainda
limita a congeneridade por meio das fronteiras de encapsulamento que execrei
no Capítulo 8 — neste caso, as fronteiras de classe das superclasses de C.
Como vimos na seção 8.2.4, a limitação de congeneridade por esse meio
simplifica a manutenção e a desdobralidade (evolvability) do sistema por duas
razões:

• Libera o desenhista das superclasses de C para redesenhar suas im-


plementações internas;
• Aperfeiçoa a compreensibilidade de C, porque ao tentarmos entender
o desenho de C, não se é continuamente forçado aos detalhes de im-
plementação das superclasses de C ou, pior, àqueles de uma classe
completamente não aparentada.

9.3 Coesão de Classe: Uma Classe e Suas Características


A coesão de classe é a medida da inter-relação das características (atributos e
operações) localizadas na interface externa de uma classe.
Na Tabela 8.2, coloquei o termo coesão de classe no quadrante formado
pelo nível-2 e nível-1 para indicar que a coesão de classe está em um nível de
encapsulamento mais alto que a coesão de módulo procedural.8 Talvez, entre-
tanto, a coesão de tipo seria até mesmo um melhor termo do que coesão de
classe, visto que, com esse conceito, estamos tentando estimar quão bem uma
classe “permanece consistente sob a forma de implementação de algum tipo de
dado abstrato”.
Uma classe com baixa (má) coesão apresenta um conjunto de caracterís-
ticas disparatadas. Uma classe com alta (boa) coesão apresenta um conjunto
de características em que todas elas contribuem para a abstração de tipo im-
plementada pela classe.
Algumas pessoas tentaram definir coesão de classe, considerando como os
métodos que implementam as operações de uma classe utilizam as variáveis
internas da classe. A idéia: quanto maior a sobreposição no uso das variáveis

8. Coesão [de módulo] procedural, um termo do desenho estruturado, é uma medida de quão
bem as linhas de código constituem juntas dentro de um único módulo procedural, tal como
uma função ou um procedimento.
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 251

no método, tanto maior a coesão da classe. Embora eu tenha tentado essa


abordagem, não a considero muito atrativa — pelas duas razões seguintes.
A primeira razão é que coesão é uma propriedade que deveria estar apa-
rente a partir do “exterior” de uma unidade de software encapsulada. Portan-
to, parece errado termos de examinar as partes internas de uma classe a fim
de estimarmos sua coesão. A segunda razão é que tal medição é instável e bas-
tante dependente do desenho interno particular de métodos, que pode mudar
durante a existência de uma classe. Portanto, uma classe imatura (a qual você
tenha iniciado o desenho) aparenta talvez ter uma coesão mais baixa do que
uma classe madura (uma que tenha desenvolvido o seu conjunto de caracte-
rísticas e implementações para uma forma adulta e final). Isso não é bom.
Durante minhas recentes visitas a locais de trabalho orientados a objeto,
observei três problemas indicativos de coesão na alocação de características
para classes: três problemas observáveis a partir do desenho externo de uma
classe. Eu denomino esses três problemas de: coesão de instância mista, coe-
são de domínio misto e coesão de papel misto. Dos três, a coesão de instância
mista é usualmente o maior problema; e a coesão de papel misto, o menor.
Uma classe pode ter todos, alguns ou mesmo nenhum desses problemas
de coesão. Uma classe isenta de coesões mistas é totalmente coesiva e é con-
siderada detentora da coesão ideal. Nas seções seguintes, defino cada uma das
três coesões mistas e avalio seus sintomas no desenho.

9.3.1 Coesão de instância mista

Uma classe com coesão de instância mista tem algumas característi-


cas que são indefinidas para alguns objetos da classe.

Por exemplo, digamos que um departamento de vendas tenha vendedores


comissionados e não comissionados. Fred é comissionado, e Mary é não comis-
sionada. Na aplicação de orientação a objeto que suporta o departamento, há
uma classe Vendedor, da qual os objetos referidos pelas variáveis fred e mary
são instâncias.
Sendo assim, a primeira destas duas mensagens faz sentido, mas a se-
gunda não:

fred.lançarPorcentagemDeComissão;
mary.lançarPorcentagemDeComissão;
252 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Poderíamos manter mensagens e fixar a comissão do objeto mary em zero.


Entretanto, isso constituiria uma mentira. Mary não tem comissão zero; ela
possui uma comissão indefinida. Mesmo assim, a classe Vendedor ainda neces-
sitaria de uma variável seComissionado:Booleano (possivelmente também dis-
ponível como atributo). E deveríamos incluir na operação Vendedor.
lançarPorcentagemDeComissão uma declaração if que evitasse a impressão de
cheques de comissão de $ 0,00 para “objetos não comissionados”.
Tudo isso funcionaria, mas seria um desenho muito mal acabado.
O problema real é que a classe Vendedor apresenta coesão de instância
mista porque ela é grosseira demais para a aplicação: ela aglomera todos os
vendedores comissionados e não comissionados como se formassem uma única
classe. Precisamos agregar as subclasses de feitio mais refinado, VendedorCo-
missionado e VendedorNãoComissionado para nosso desenho. Essas duas novas
subclasses serão herdeiras da superclasse delas, Vendedor. Um vendedor co-
missionado seria representado por uma instância de VendedorComissionado,
enquanto um vendedor não comissionado seria representado por uma instân-
cia de VendedorNãoComissionado.
A operação lançarPorcentagemDeComissão seria então alocada para Vende-
dorComissionado, conforme mostrado na Figura 9.5.

Figura 9.5 Remover o problema de coesão da classe


Vendedor ao agregar subclasses.

A coesão de instância mista normalmente indica uma hierarquia de clas-


se que não é bem refinada ou que simplesmente não é correta. Como foi visto,
isso também conduz a um pouco de código “retorcido” (sob a forma de decla-
rações extras if) dentro da própria classe atingida.
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 253

9.3.2 Coesão de domínio misto

Uma classe com coesão de domínio misto contém um elemento que di-
retamente cria dependência da classe em relação a uma classe extrín-
seca de um domínio diferente.

Nessa definição, utilizo domínio no mesmo sentido utilizado na seção 9.1.


Mas agora preciso definir extrínseco.

A classe B é extrínseca de A se A puder ser plenamente definida sem


qualquer noção de B.
A classe B é intrínseca de A se B capturar algumas características
inerentes de A.

Por exemplo, a classe Elefante é extrínseca à classe Pessoa, porque de for-


ma alguma “elefante” captura alguma característica de uma pessoa. Entretan-
to, Data (como em: data de nascimento) é intrínseca à Pessoa.
Há muitos exemplos de coesão de domínio misto, alguns dos quais são ób-
vios, outros sutis. O primeiro exemplo que observei era sutil: era a classe dos
números reais, NúmeroReal, em uma biblioteca de classes de um distribuidor,
a qual tinha um atributo arctang (arco tangente).9 Eu fiquei olhando fixamen-
te para esse atributo por um longo tempo, pois percebi algo extremamente er-
rado com sua alocação para a classe NúmeroReal. Entretanto, embora eu tenha
percebido a gravidade do problema, não consegui descobrir o motivo pelo qual
achei que arctang não era pertencente à classe NúmeroReal.
Um dia, enquanto estava sentado debaixo de uma macieira em meu jar-
dim, um tipo de visão me ocorreu de forma repentina. (À falta de certo sentido
de precedente histórico, exclamei “Eureca!”) NúmeroReal não tem qualquer
tipo de negócio interferindo com os objetos da classe Ângulo. Onde esse dese-
nhista pararia com essa forma de grau de dependência?
Que tal acrescentar uma operação denominada converterTemperatura
para a classe NúmeroReal de forma que poderíamos converter Fahrenheit a
Celsius e Kelvin a Réaumur? Isso iria criar dependência de NúmeroReal com
Temperatura. Ou, caso quiséssemos trocar dólares por euros, criar dependência
NúmeroReal com Dinheiro. Ou NúmeroReal poderia até mesmo retornar o con-

9. No caso de sua trigonometria escolar estar um pouco enferrujada, eu deveria explicar que a
função arctang é a função inversa da tangente. Ela toma um número real r e lhe diz o ângulo
cuja tangente é r. O arctang de 1,0 é 45 graus, por exemplo. (arccos é a função inversa do
co-seno).
254 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

junto de clientes cujos saldos bancários fossem (até o último centavo) igual a
algum número real. A lista de possibilidades absurdas é interminável, confor-
me mostrado pela Figura 9.6.

Figura 9.6 NúmeroReal com coesão de domínio misto desenfreada.

Quando você desenhar uma classe de um dado domínio, você terá de in-
cluir classes dos domínios mais baixos em seu projeto — isso é inerente à reu-
tilização. Mas certifique-se de que você precisa realmente dessas classes por
causa das propriedades intrínsecas de sua classe. Por exemplo, seria inteira-
mente satisfatório para a classe Conta ter uma operação que retornasse um
objeto da classe Dinheiro ou mesmo um atributo da classe Data. Entretanto, eu
ficaria com muitas desconfianças se a classe retornasse um objeto da classe do
domínio de arquitetura, WireXferLink.
O domínio de arquitetura muitas vezes acaba misturado em uma classe
do domínio de negócio. Isso é errado — a menos que seu negócio esteja real-
mente edificando infra-estruturas arquiteturais. O antigo lema orientado a
objeto de “uma coisa deveria saber como fazer algo ela própria”, como em “um
documento deveria saber como imprimir ele próprio”, pode ser um pouco
atraente demais. Uma classe Documento, com conhecimento específico de uma
impressora, tem coesão de domínio misto.
Ao desenhar uma classe de um dado domínio, você deveria ser particu-
larmente cauteloso quanto à introdução de classes de domínio mais alto na
classe que você está desenhando. Esse é o motivo pelo qual anteriormente a
classe NúmeroReal era um problema. Número Real é uma classe de base, e não
deveria criar dependência com classes de domínio mais alto, ainda que, no de-
senho sem recursos visto por mim, o atributo arctang de NúmeroReal (da classe
Ângulo) tenha forçado NúmeroReal a lidar com Ângulo, uma classe de um grupo
mais alto de classes (a saber, do grupo semântico).
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 255

Outra forma de se compreender os domínios relativos de duas classes é a


de se fazer a seguinte pergunta: será que posso imaginar esta classe sendo
construída sem esta outra classe? Eu consigo pensar em NúmeroReal sendo
construído sem a classe Ângulo já existente. Todavia, não consigo visualizar a
construção de uma classe Ângulo sem a classe NúmeroReal. Isso implica que
Ângulo está em um domínio mais alto que NúmeroReala.
(Incidentalmente, a classe NúmeroReal que encontrei tinha outro atributo,
arccos [arco co-seno], que nem mesmo é definido para a maioria dos números
reais. Isso igualmente aumentou a dependência da pobre classe com a coesão
de instância mista.)

9.3.3 Coesão de papel misto

Uma classe C com coesão de papel misto contém um elemento que di-
retamente cria dependência dessa classe com uma classe extrínseca
que reside no mesmo domínio de C.

Diferentemente da classe com coesão de domínio misto, uma classe com


coesão de papel misto não “alarga muito” os domínios. Entretanto, ela de fato
inclui mais do que um papel desde um único domínio (“papel”, nesse caso, sig-
nifica uma abstração de um grupo de coisas afins no mundo real).
Um exemplo famoso inclui pessoas e cachorros. Digamos que tivéssemos
uma classe Pessoa com um atributo númeroDeCachorrosPossuídos (o número de
cachorros que uma dada pessoa tem). Se a operação obter para o atributo cor-
responde a uma função simples (também denominada númeroDeCachorrosPos-
suídos), utilizamos fred.númeroDeCachorrosPossuídos para descobrir quantos
cachorros são possuídos por um objeto fred (da classe Pessoa).
A classe Pessoa não tem coesão de instância mista, porque objetos não
possuidores de cachorros poderiam retornar simplesmente um valor igual a
zero. Ela não tem coesão de domínio misto, porque Pessoa e Cachorro estão am-
bos no domínio de negócio. Porém ela tem coesão de papel misto porque Pessoa
e Cachorro são conceitos distintos, extrínsecos um em relação ao outro.
Em termos puros de desenho, a coesão de papel misto é a transgressão
menos séria existente da coesão de classes. Todavia, se você estiver desenhan-
do suas classes para fins de reutilização, então você deverá prestar muita
atenção à coesão de papel misto. O que aconteceria se você quisesse reutilizar
Pessoa em uma aplicação que não tivesse cachorros? Você poderia utilizá-la,
mas teria uma bagagem extra e inútil na classe, e talvez recebesse alguns avi-
sos desagradáveis sobre cachorros perdidos em seu compilador (ou linker).
256 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

E onde devemos parar com essa filosofia de desenho? Por que não incluir
esses atributos em Pessoa: númeroDeCarrosPossuídos, númeroDeBarcosPossuí-
dos, númeroDeGatosPossuídos, númeroDeSaposPossuídos...? Não apenas esses
atributos criariam dependência de forma severa em relação à classe Pessoa
com outras classes, como também cada um deles, ainda implicaria outro esta-
belecimento de operação para a atualização do número de coisas possuídas.10
A coesão de papel misto, conforme exemplificado por “pessoa possui ca-
chorro”, também é perfeitamente atraente porque:

• é fácil escrever o código de mensagem para descobrir quantos cachor-


ros alguém possui: simplesmente se escreve fred.númeroDeCachorros-
Possuídos;
• muitas abordagens de modelagem de informações na realidade tra-
tam de númeroDeCachorrosPossuídos como um atributo de Pessoa;
• na vida real, se você quiser descobrir quantos cachorros o Fred possui,
provavelmente perguntará ao próprio Fred: “Ei, quantos cachorros
você têm?”.11

Entretanto, por mais atrativos que sejam esses argumentos, você não de-
veria se distrair ao criar automaticamente uma classe Pessoa com coesão de
papel misto. Existem muitas outras opções de desenho que não comprometem
a coesão de Pessoa; eu exploro quatro dessas opções em detalhes no exercício
final do Capítulo 14.
Na qualidade de desenhista orientado a objeto, você deveria visar a cria-
ção de classes com coesão ideal, ou seja, classes destituídas de coesão de ins-
tância mista, de domínio misto ou de papel misto. As classes com coesão ideal
apresentam uma reutilização máxima, e isso é o objetivo principal de muitos
locais de trabalho voltados à orientação a objeto (particularmente para as
classes no domínio de negócio).

9.4 Resumo
Uma classe pode pertencer a um entre quatro domínios. Esses domínios são:
o domínio de base, que compreende classes de valor em todos os negócios e ar-
quiteturas; o domínio de arquitetura, que compreende classes de valor para
uma arquitetura de implementação; o domínio de negócio, que compreende

10. Sim, você poderia generalizar todas essas operações para númeroDeCoisasPossuídas usando
tipoDeCoisa passado como um argumento. Mas o problema básico de coesão permaneceria.
11. Acho essa visão antropomórfica da orientação a objeto capciosa e irrelevante. Eu a incluí por-
que muitas vezes ouvi sobre ela como um “princípio de desenho” orientado a objeto.
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 257

classes de valor para uma indústria ou companhia; e o domínio de aplicação,


que compreende classes (bem como algumas construções procedurais mais
simples) de valor no interior de uma aplicação.
As classes no domínio de base apresentam maior reutilização, ao passo
que as do domínio de aplicação apresentam a menor reutilização. (Isso, é cla-
ro, decorre diretamente das definições dos domínios.) Cada um dos quatro do-
mínios tem, dentro dele, diversos grupos de classes.
O grau de dependência direto é o tamanho do conjunto classe-referência
direto de uma classe, o qual é o conjunto de outras classes às quais a classe
diretamente se refere. O grau de dependência indireto é o tamanho do conjun-
to de referência indireto de uma classe, o qual é o conjunto formado pelo fe-
chamento transitivo do conjunto classe-referência direto da classe. Falando
informalmente, o grau de dependência indireto de uma classe é o número de
outras classes de que ela necessita para poder operar.
As classes dos domínios mais altos normalmente têm grau de dependên-
cia indireto mais alto do que as classes dos domínios mais baixos. A Lei de De-
méter (nomeada após o Projeto de Deméter, na qual ela foi primeiramente
postulada) oferece certas diretrizes para restringir o grau de dependência di-
reto de uma classe, limitando suas referências a outras classes.
A coesão de uma classe é a medida de quão bem as características da
classe (suas operações e atributos) constituem juntas uma única classe. Uma
classe afasta-se da coesão ideal se ela tiver coesão de instância mista, de do-
mínio misto ou de papel misto. (Ela pode ter mais de um tipo de coesão. Veja
o exercício 4 a seguir).
Uma classe com coesão de instância mista tem elementos que são indefi-
nidos para alguns objetos gerados desde a classe. Uma classe com coesão de
domínio misto tem um elemento que se refere a uma classe extrínseca perten-
cente a um domínio diferente. Uma classe com coesão de papel misto tem um
elemento que se refere a uma classe extrínseca pertencente ao mesmo domí-
nio.
Desses três tipos de coesão, a coesão de instância mista é a que apresenta
os melhores resultados no tocante ao desenho e a problemas de manutenção,
enquanto a coesão de papel misto tende a apresentar os piores resultados.

9.5 Exercícios
1. Uma biblioteca de classes que você comprou de um distribuidor de classes
gerais provavelmente conterá somente classes de base. Por que você acha
que isso ocorre?
258 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2. Como devemos lidar com a herança quando calcularmos o grau de depen-


dência? Por exemplo, algumas hierarquias de classe emanam de uma úni-
ca classe raiz (nomeada, por exemplo, Objeto ou Qualquer). Visto que cada
uma das classes em uma hierarquia desse tipo são, em última análise,
herdeiras de Objeto, isso não implica que os graus de dependência indi-
retos de todas as classes serão aproximadamente iguais?
3. A classe Hominóide do Capítulo 1, conforme desenhada, tinha problemas
de coesão? Em caso afirmativo, sugira um desenho alternativo para Ho-
minóide.
4. Se uma classe é capaz de ter coesão de instância mista, de domínio misto
ou de papel misto, quantas combinações de coesão são possíveis para uma
classe?
5. Em uma certa ocasião, vi uma classe ClienteDeBanco com um atributo
sexo, que retornava o sexo de um cliente como sendo de quatro valores
possíveis: 0 (sexo do cliente era desconhecido), 1 (masculino), 2 (feminino)
e — este me pegou de surpresa — 3 (outros!). Esse último valor veio a
ser utilizado para os clientes de bancos constituídos de empresas, cujo
sexo é indefinido.
Qual é a coesão de ClienteDeBanco? Que alteração você introduziria no de-
senho para melhorar a coesão dele?
6. Se você tiver uma série de atributos trigonométricos, tais como tang, cos,
sen e assim por diante, você pode torná-los atributos de instância da clas-
se Ângulo. Atributos tais como arctang, arccos, arcsen e assim por diante,
constituiriam operações de classe da classe Ângulo, pelo fato de eles não
se aplicarem a instâncias individuais de Ângulo. De que outra forma (di-
ferentemente de propriedades de classe e de instância) você conseguiria
reunir todas essas características em uma única construção? (Dica: a
construção apareceu na seção 3.8.)
7. Investigue uma parte do software comercialmente disponível com a qual
você está acostumado. Ele é construído com qualquer forma de separação
de domínios? Em particular, ele é construído de forma que as classes nos
domínios mais baixos evitam referir-se às encontradas nos domínios mais
altos?

9.6 Respostas
1. Há diversas razões: em primeiro lugar, a maioria dos distribuidores visa
o mercado que tenha o máximo potencial comercial. Por definição, as clas-
Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 259

ses de base são proveitosas para o grupo mais amplo possível de indús-
trias e aplicações. Em segundo lugar, a maioria das classes de base de-
manda um conhecimento da informática em geral, mais que de qualquer
negócio em particular. Conseqüentemente, um distribuidor típico de clas-
ses pode reivindicar a si próprio (com credibilidade) um conhecimento das
classes de base tão bom quanto o de qualquer outro tipo de classe.
E, em terceiro lugar — e essa é a razão pela qual as bibliotecas da maio-
ria dos distribuidores não contêm classes de negócio —, essas classes são
difíceis de serem analisadas e freqüentemente incorporam informações e
planos de ação de negócios que as companhias consideram propriedade
delas. A maioria das companhias não se encontra atualmente na ativida-
de comercial de negociar suas especializações de negócios sob a forma de
classes de software para fins de reutilização.
Todavia, acredito que essa situação mudará e que, breve, veremos biblio-
tecas de classes do domínio de negócio específicas para indústria à venda
no mercado, inclusive bibliotecas de classes destinadas a atividades ban-
cárias, telecomunicações e serviços médicos. No momento, muitas das
forças-tarefa do OMG — Object-Management Group — estão se empe-
nhando em trabalhos voltados a componentes orientados a objeto especí-
ficos para indústria.
2. Não. Uma classe muito provavelmente não derivará uma quantidade sig-
nificativa de seu grau de dependência indireto de uma classe mais alta
na hierarquia de herança de classes por duas razões. Em primeiro lugar,
a herança é apenas um meio pelo qual uma classe se refere a outras clas-
ses; o grau de dependência de uma classe é devido também a muitos ou-
tros meios de referência. Em segundo lugar, as classes mais altas em
uma hierarquia de herança de classes tendem a ter um grau de depen-
dência que varia de baixo a médio, porque elas se referem a algumas ou-
tras classes. Por conseguinte, a contribuição de uma classe situada nas
partes altas da hierarquia para o grau de dependência de suas subclasses
provavelmente não será dramática.
Todavia, uma variação do grau de dependência indireto (recentemente,
estive em um local de trabalho que trabalhava sobre ela) exclui de um
conjunto classe-referência indireto de uma classe C quaisquer classes cu-
jas características não sejam realmente utilizadas pelos métodos de C.
Essa variedade de grau de dependência indireto pode render uma medida
mais acurada do grau de dependência de C, mas é também mais compli-
cada de se calcular do que a descrita por mim neste capítulo.
260 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

3. A classe Hominóide apresentava coesão de domínio misto, visto que ela ti-
nha uma operação exibir que mostrava um hominóide em uma tela. As-
sim, uma vez que Hominóide é presumivelmente misturado com
dispositivos e seus formatos, até mesmo podemos precisar de uma versão
diferente de Hominóide para cada uma das arquiteturas (impressora,
plotter, monitor) presentes no local de trabalho. É possível até mesmo que
nós — nem é bom pensar nisso! — solicitássemos uma versão diferente
para toda e qualquer resolução possível de monitor.
Se isso não o incomodar (provavelmente porque portabilidade e reutiliza-
ção não são problemas), então tudo bem! Mas, do contrário, você deverá
ter a classe Hominóide retornando uma referência para um objeto que de-
tenha o formato de exibição do hominóide (que seria da classe ModoBit ou
ModoLinha). Assim, você poderia enviar o objeto em uma mensagem para
um objeto de um dispositivo de saída, que, em seguida, o exibiria.
Entretanto, você talvez não goste dessa abordagem de desenho, porque
você pode considerar que a exportação da representação do padrão de bit
de um hominóide não devia ser parte da abstração de Hominóide. Se você
desejar capacitar uma operação exibir para acessar informação sobre a
aparência de um hominóide, mas, ao mesmo tempo, não quiser exportar
essa informação de Hominóide, você poderá criar outra classe, Hominói-
deExibível, herdeira de Hominóide, conforme mostrado na Figura 9.7. A
operação exibir, definida em HominóideExibível, operaria em variáveis de-
finidas no interior de Hominóide.

Figura 9.7 O desenho de HominóideExibível.


Cap. 9 DOMÍNIOS, GRAU DE DEPENDÊNCIA E COESÃO 261

A desvantagem desse desenho é que o acesso à informação sobre o homi-


nóide, dentro da classe Hominóide, está estreitamente vinculado à classe
HominóideExibível para a classe Hominóide. Isso gera o risco de que alte-
rações no desenho interno de Hominóide se farão sentir em HominóideExi-
bível.
Outra abordagem de desenho possível é separar a aparência de um ho-
minóide de suas demais informações e manter a “aparência do hominói-
de” em outra classe. Eu trato dessa abordagem de desenho na seção
12.1.4, quando examino o desenho da classe Sala.
4. A Tabela 9.1 mostra as seis possíveis combinações de coesão de instância
mista, de domínio misto e de papel misto que uma classe pode possuir. A
razão de existirem seis (em vez de oito) combinações é que uma classe
com coesão de domínio misto deve ter, ainda, coesão de papel misto, con-
forme as combinações 3 e 6 da Tabela 9.1 indicam. A primeira combina-
ção representa a coesão de classe ideal. (Na tabela, as abreviações IM,
DM e PM significam, respectivamente, coesão de instância mista, de do-
mínio misto e de papel misto.)

Tabela 9.1
As seis possíveis combinações de coesão de instância mista,
de domínio misto e de papel misto12.

Combinação IM DM PM

1 N N N

2 N N S

3 N S S

4 S N N

5 S N S

6 S S S

5. ClienteDeBanco tem coesão de instância mista, visto que um de seus atri-


butos (sexo) aplica-se a somente algumas de suas instâncias (ou seja, so-
mente a objetos representativos de clientes pessoas físicas). O desenhista
dessa classe deveria decompô-la em duas subclasses, ClienteDeBancoPes-

12. N.R.T.: Na Tabela 9.1, N significa não; S significa Sim.


262 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

soa e ClienteDeBancoEmpresa. A primeira subclasse teria atributos tais


como sexo e nomeDeSolteira. A segunda subclasse teria atributos tais
como tipoDeEmpresa e númeroDeAcionistas. A superclasse ClienteDeBanco
teria atributos tais como identificaçãoDeCliente e nomeDeCliente (supondo
que os clientes pessoas jurídicas tenham o mesmo formato de nome que
os clientes humanos).
Uma vez que essas duas subclasses tenham sido decompostas desde Clien-
teDeBanco todas as três classes terão coesão ideal (contanto que elas não
tenham, é certo, outros problemas de coesão, tais como coesão de domínio
misto).
6. Você poderia reunir essas categorias como um pacote de utilidade de fun-
ções trigonométricas, com assinaturas tais como:

tang (ângulo: Ângulo): NúmeroReal;


arctang (númeroreal: NúmeroReal): Ângulo;

Essa abordagem de desenho evitaria ainda o problema de coesão de do-


mínio misto que ressaltei na seção 9.3.2.
7. Uma parte dos “kits para construção de aplicações”, baseados em compo-
nentes tem exatamente a espécie de separação de domínios que discuto
neste capítulo, por meio da qual há uma estrita hierarquia social de re-
ferências, desde as construções de software no domínio de aplicação, pre-
sentes na parte superior, descendo em direção àquelas que ficam no
domínio de base na parte inferior.
E spaço-estado e comportamento
FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML
ESPAÇO-ESTADO E COMPORTAMENTO
10.Espaço-estado e Comportamento

N a primeira seção deste capítulo, introduzo duas propriedades fundamen-


tais de classes: espaço-estado e comportamento. Na segunda seção, explo-
ro os espaços-estados de subclasses. Na terceira seção, analiso o compor-
tamento de subclasses.
A quarta seção introduz a invariante de classe como uma restrição sobre
o espaço-estado. A quinta e última seção explora as precondições e pós-condi-
ções de operação, as quais juntas formam o contrato entre uma operação e um
cliente dessa operação.
As invariantes de classe, e as precondições e pós-condições, formam a es-
pinha dorsal de uma abordagem de desenho orientado a objeto denominada
desenho por comprometimento, ou contrato, (design by contract). O próximo ca-
pítulo, fundamentado nestes conceitos, explora meios de se atingir desenhos
de hierarquia de classes robustos.

10.1 Espaço-Estado e Comportamento de uma Classe

Uma classe deve representar uma abstração uniforme das proprieda-


des dos objetos individuais que pertencem a essa classe.

Embora essa frase soe um tanto pomposa, o que as palavras nela contidas
significam na realidade?
Com abstração, quero dizer que não necessariamente temos de considerar
toda propriedade possível das coisas do mundo real representada por objetos
de software. Por exemplo, embora possamos ter uma classe ClienteDeBancoPes-

263
264 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

soa, não temos de incluir a propriedade tamanhoDeCabeça nessa classe (se


bem que poderíamos fazer isso caso quiséssemos).
Com uniforme, quero dizer que a abstração escolhida para uma classe
aplica-se da mesma forma para cada um dos objetos pertencentes à classe.
Por exemplo, se estivermos interessados na data de nascimento de Clien-
teDeBancoPessoa , então estamos interessados na data de nascimento de todos
os clientes de banco pessoas físicas.
Com propriedades, quero dizer simplesmente isto:

As duas propriedades de uma classe são o espaço-estado e o comporta-


mento permitido desta.

Uma grande parte deste capítulo é dedicada à exploração dos conceitos de


espaço-estado e comportamento, e de suas implicações práticas quanto ao de-
senho orientado a objeto. Mas, antes de definir formalmente espaço-estado e
comportamento, deixe-me ilustrar esses dois termos com um exemplo concreto:
uma peça de xadrez o (cavalo) sobre um tabuleiro de xadrez — isto é, um ob-
jeto da classe CavaloDeJogoDeXadrez — conforme mostrado na Figura 10.1.

Figura 10.1 Tabuleiro de xadrez com uma rainha e um cavalo.

O espaço-estado total de CavaloDeJogoDeXadrez importa em todos os qua-


drados (casas) do tabuleiro. Em seguida, consideremos a rainha. O espaço-es-
tado total de RainhaDeJogoDeXadrez, igualmente, corresponde a todas as casas
do tabuleiro. Todavia, sabemos que a classe RainhaDeJogoDeXadrez é diferente
da classe CavaloDeJogoDeXadrez. Dessa forma, o que é diferente? A resposta é:
o comportamento dela.
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 265

A rainha pode mover-se ao longo de qualquer linha, coluna ou diagonal,


para atingir outra casa. Portanto, ela pode atingir qualquer outra casa em um
tabuleiro vazio — em outras palavras, qualquer outro estado em seu espaço-
estado — apenas com dois movimentos. O cavalo, por outro lado, com seu com-
portamento peculiar, talvez precise de até seis movimentos para atingir outra
casa.
Por conseguinte, as classes CavaloDeJogoDeXadrez e RainhaDeJogoDeXa-
drez apresentam espaços-estados idênticos mas comportamentos desiguais.
Agora, imaginemos outra espécie de cavalo, um que se mova como o cavalo
convencional, mas que não tenha permissão de posicionar-se nas quatro casas
centrais do tabuleiro de xadrez. (Veja a Figura 10.2).

Figura 10.2 Cavalo que não tem permissão de


posicionar-se no centro de um tabuleiro.

Dessa forma, por que esse cavalo é diferente? A resposta é que, embora
ele compartilhe do mesmo comportamento que o cavalo tradicional, o espaço-
estado dele difere.
Do que já foi dito, vimos que duas classes podem diferir, seja no tocante
aos seus espaços-estados, seja nos seus comportamentos (ou em ambos). Ago-
ra, definirei espaço-estado de maneira mais exata. (Definirei comportamento
na seção 10.3.)
266 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

O espaço-estado de uma classe C é a totalidade de todos os estados


permitidos de qualquer objeto da classe C.

As dimensões de um espaço-estado são as coordenadas necessárias


para especificar o estado de um dado objeto.

Informalmente, o estado de um objeto é o “valor” que ele tem em deter-


minada hora. Mais propriamente, o estado de um objeto é o conjunto ordenado
de objetos aos quais os objetos se referem em determinado tempo. Por exem-
plo, o estado de um objeto Piscina, neste momento, poderia ser 30 metros de
comprimento, 2 metros de profundidade e 25oC. Em outras palavras, esse ob-
jeto Piscina, atualmente, aponta para um objeto da classe Comprimento (30 me-
tros), para um da classe Comprimento (2 metros) e para um outro da classe
Temperatura (25oC).
Eu ilustro o espaço-estado de uma classe como uma grade de pontos, cada
ponto configurando um estado. Cada objeto, que representa uma instância da
classe, é como um pequeno ponto, que passa sua vida “pulando em volta” de
lugar para lugar (de estado para estado) dentro do espaço-estado de sua clas-
se. O “salto de lugar para lugar” é conhecido tecnicamente como transição (no
mesmo sentido que utilizei transição no Capítulo 6).
A Figura 10.3 mostra essa grade em três dimensões — o espaço-estado
para a classe LinhaDeProduto. (Muito embora o espaço-estado da verdadeira
classe possa ter muito mais dimensões, três é o número máximo delas que eu
sou capaz de desenhar!) As três dimensões mostradas por mim são peso, preço
e quantidadeDisponível. Visto que estas três dimensões são mutuamente inde-
pendentes, um objeto (uma dada linha de produto) pode ser encontrado(a) em
praticamente todos os lugares da grade. Diversos objetos repousam como pe-
quenos pontos no interior da grade LinhaDeProduto, conforme mostrado na Fi-
gura 10.3.
Como outro exemplo, a classe Paciente pode ter essas dimensões como ida-
de, altura, peso, temperaturaAtual e assim por diante. Um dado objeto Paciente
talvez esteja em (praticamente!) qualquer ponto nessa grade multidimensional.
As dimensões de espaço-estado de uma classe são aproximadamente equi-
valentes aos atributos definidos na classe.1 Os valores marcadores das dimen-
sões são os valores que os atributos podem assumir. Uma vez que cada
dimensão é característica de alguma classe, os valores ao lado dessas dimen-

1. Eu disse “aproximadamente” porque a maioria dos atributos que podem ser derivados de ou-
tros não são normalmente considerados dimensões. Por exemplo, as dimensões de um Cubo
podem ser comprimento, largura e altura, mas não volume ou área.
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 267

sões são objetos da classe da dimensão. Por exemplo, a dimensão preço de Li-
nhaDeProduto seria marcada com objetos da classe Dinheiro, e a altura de um
paciente seria marcada em polegadas ou centímetros, objetos da classe Com-
primento.

Figura 10.3 O espaço-estado da classe LinhaDeProduto sob a forma de uma grade,


com cada linha de produto representada como um “pequeno ponto”.

As classes marcadoras de dimensões nem sempre são provenientes de um


domínio tão baixo como Dinheiro e Comprimento. Por exemplo, o espaço-estado
da classe Martelo tem duas dimensões, Cabo e Cabeça, porque você faz um
martelo (um objeto composto) selecionando um cabo e uma cabeça (os dois ob-
jetos componentes).2 Cada uma destas duas classes é propriamente dita do do-
mínio de negócio e em si tem diversas dimensões (tal como comprimento, peso
e assim por diante).

10.2 O Espaço-Estado de uma Subclasse


Esta seção começa nossa exploração em subclasses com um exame dos espa-
ços-estados de duas classes ilustres, A e B.

2. Para ser coerente, eu deveria denominar essas dimensões de martelo (da classe Martelo) e
cabeça (da classe Cabeça). Discuto dimensões mais adiante, no exercício 1, no final deste ca-
pítulo.
268 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Se B é uma subclasse de A, então o espaço-estado de B deve estar con-


tido inteiramente dentro do espaço-estado de A.3 Dizemos que o espa-
ço-estado de B é confinado pelo espaço-estado de A.

A melhor forma de escalrecer isso é dar um exemplo. Digamos que a clas-


se A é VeículoRodoviário. Por simplicidade, assumiremos que seu espaço-estado
tenha apenas uma dimensão: pesoAtual. Especificaremos os limites inferior e
superior como, respectivamente, 0,5 e 10,0 tons em VeículoRodoviário.pesoA-
tual.
Suponha que a subclasse B seja Automóvel. Se fixarmos os limites inferior
e superior como, respectivamente, 1,0 e 3,0 tons em Automóvel.pesoAtual, en-
tão estaremos em uma boa forma. O espaço-estado de Automóvel está confina-
do dentro do espaço-estado de VeículoRodoviário, e, portanto, Automóvel se
encontra perfeito como uma subclasse de VeículoRodoviário.
A Figura 10.4 mostra graficamente os limites de variação de VeículoRo-
doviário.pesoAtual e Automóvel.pesoAtual.

Figura 10.4 Os estados-espaços 1-D de Automóvel e Veículo Rodoviário.

Observe que, se tivéssemos especificado os limites inferior e superior de


respectivamente 0,2 e 13,0 tons em Automóvel.pesoAtual, estaríamos em difi-
culdades. Por exemplo, um automóvel de 13 tons seria ilegal segundo a defi-
nição acima de VeículoRodoviário, que limita um veículo rodoviário a 10 tons.
Portanto, o espaço-estado de Automóvel não estaria confinado no espaço-esta-
do de VeículoRodoviário, e, assim, Automóvel não poderia ser uma subclasse de
VeículoRodoviário.
Essa violação de espaço-estado permitiria que um objeto fosse uma ins-
tância válida de sua própria classe, mas ilegal como uma instância da super-

3. Tecnicamente, é a projeção do espaço-estado de B no espaço-estado de A que deve residir den-


tro do espaço-estado de A.
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 269

classe. Em outras palavras, meu carro poderia ser um automóvel mas não um
veículo rodoviário — um absurdo.
Ironicamente — e isso surpreende muitas pessoas — o espaço-estado de
uma subclasse pode ter mais dimensões que o espaço-estado da superclasse.

Se B é uma subclasse de A, então o espaço-estado de B deve com-


preender, no mínimo, as dimensões do espaço-estado de A — mas ele
pode compreender ainda mais dimensões. Se compreender mais di-
mensões, dizemos que o espaço-estado de B prolonga-se a partir do es-
paço-estado de A.

O espaço-estado de Automóvel, por exemplo, poderia ter a dimensão Au-


tomóvel.contagemAtualDePassageiro, a qual não caracterizaria uma dimensão
de um veículo rodoviário geral não destinado a passageiro. Se assim fosse, o
espaço-estado de Automóvel seria uma extensão do espaço-estado de Veículo-
Rodoviário.
Talvez isso tenha lhe deixado um pouco aturdido. Como pode o espaço-
estado de uma subclasse ser ao mesmo tempo confinado pelo (e prolongado a
partir do) espaço-estado de sua superclasse? A resposta é esta: dentro das di-
mensões definidas na superclasse, o espaço-estado de uma subclasse pode ser
menor que o da superclasse — mas, ao mesmo tempo, o espaço-estado da sub-
classe pode se estender em outras dimensões indefinidas para a superclasse.
Por exemplo, a Figura 10.5 mostra o espaço-estado de Automóvel simultanea-
mente confinado pelo (e prolongado a partir do) espaço-estado de VeículoRodo-
viário.
Na dimensão pesoAtual, o espaço-estado de Automóvel continua confinado
no espaço-estado de VeículoRodoviário porque a faixa de Automóvel.pesoAtual
ainda cai dentro da faixa de VeículoRodoviário.pesoAtual. Agora, entretanto,
Automóvel tem uma dimensão extra (contagemAtualDePassageiro) em seu espa-
ço-estado. Dessa forma, seu espaço-estado (mostrado como uma área cinza na
Figura 10.5) prolonga-se naquela segunda dimensão; especificamente, confor-
me vimos, um automóvel pode ter de zero a sete passageiros a bordo. Para a
classe mais geral, VeículoRodoviário, a contagemAtualDePassageiro permanece
indefinida.
270 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Figura 10.5 O espaço-estado 2-D de Automóvel e o


espaço-estado 1-D de VeículoRodoviário.

10.3 O Comportamento de uma Subclasse


A maioria dos objetos (a menos que eles sejam imutáveis) faz transições den-
tro do espaço-estado de suas classes quando um ou mais de seus atributos mu-
dam de valor. Essas transições constituem um comportamento permitido de
uma classe, o qual é definido da seguinte forma:

O comportamento permitido de uma classe C é o conjunto de transi-


ções que um objeto da classe C pode fazer entre estados no espaço-es-
tado de C.

Essa definição implica que nem todas as transições possíveis são válidas
para um objeto. Um objeto fica “pulando em volta” dentro do espaço-estado de
sua classe somente da maneira prescrita para ele pelo comportamento da clas-
se. Como vimos na seção 10.1, embora um cavalo possa ser encontrado em
qualquer uma das 64 casas do tabuleiro de xadrez, suas transições permitidas
são bastante limitadas (no máximo, oito transições desde a sua casa atual).
E o comportamento em subclasses? Os comportamentos de superclasse e
subclasse apresentam alguma relação parecida com o dos espaços-estados? Es-
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 271

pecificamente, o comportamento se prolonga a partir de uma subclasse e/ou se


confina nela?
A resposta para ambas as perguntas é sim. Em primeiro lugar, vamos
considerar a extensão, o comportamento que B (a subclasse) possui, mas do
qual A (a superclasse) se ressente. É óbvio que esse comportamento extra em
B deve existir, pois, sem ele, como uma instância de B conseguiria saltar para
a parte do espaço-estado de B que se estende para fora do espaço-estado de A?
Vamos voltar a VeículoRodoviário e sua subclasse Automóvel de nosso
exemplo. Automóvel, o comportamento de facilitado por operações como apa-
nharPassageiro e deixarPassageiro, fará com que ele aumente e diminua o seu
número de passageiros. Esse comportamento ultrapassa claramente qualquer
comportamento que VeículoRodoviário possa ter; afinal de contas, VeículoRodo-
viário nem mesmo tem uma noção do que seja passageiros!
(Para colocar isso em termos mais técnicos: falta à classe VeículoRodoviá-
rio a dimensão de contagemAtualDePassageiro em seu espaço-estado. Portanto,
qualquer comportamento que permita a um objeto “caminhar para fora” da di-
mensão contagemAtualDePassageiro, possivelmente não poderia ser definido
em VeículoRodoviário.)
Esse exemplo mostra que Automóvel talvez estenda o comportamento de
sua superclasse, VeículoRodoviário, da mesma forma que Automóvel prolongou
o espaço-estado de VeículoRodoviário.
O comportamento de Automóvel também pode ser confinado pelo compor-
tamento de VeículoRodoviário, da mesma forma que o espaço-estado de Auto-
móvel foi confinado pelo espaço-estado VeículoRodoviário. Um exemplo simples:
nós possivelmente podemos acrescentar cinco toneladas ao peso de um veículo
rodoviário comum, contanto que ele não exceda o limite de dez toneladas. En-
tretanto, jamais poderemos acrescentar cinco toneladas ao peso de um auto-
móvel porque o seu peso máximo é de três toneladas; senão, removeríamos ele
do espaço-estado de Automóvel.
Na seção 11.3, volto ao confinamento de comportamento, cujas implica-
ções conduzem a um importante princípio no desenho de subclasses à prova
de bala: comportamento fechado.

10.4 A Invariante de Classe Como Restrição em um


Espaço-Estado
A maioria dos espaços-estados que examinamos neste capítulo tem sido com-
pleta, ou seja, um objeto pode ocupar qualquer um dos lugares na grade de
espaço-estado. Entretanto, muitas classes não capacitam seus objetos a esse
272 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

tipo de liberdade. Vimos uma dessas restrições no exemplo da peça de xadrez


(cavalo), a qual tinha de manter-se afastada do centro do tabuleiro. Nesta se-
ção, procuramos uma maneira de definir espaços-estados válidos com mais
precisão.
O espaço-estado válido de uma classe é definido pela sua invariante de
classe.

Uma invariante de classe é uma condição que todo objeto dessa clas-
se deve sempre satisfazer (quando o objeto está em equilíbrio).

A expressão “quando o objeto está em equilíbrio” significa que um objeto


deve obedecer a sua invariante de classe todas as vezes em que não estiver no
meio de estados em transição. Em particular, um objeto deve tocar a linha in-
variante quando o objeto for inicializado na geração, e antes e depois de qual-
quer operação (pública) ser executada.
Vamos tomar como exemplo a classe Triângulo.4 Se os lados de um triân-
gulo são Triângulo.a, Triângulo.b e Triângulo.c, então parte da invariante de
classe de Triângulo seria:

a + b ≥ c and b + c ≥ a and c + a ≥ b

Essa expressão afirma que, não importa qual objeto da classe Triângulo ti-
vermos, a soma dos comprimentos de dois quaisquer lados da figura deverá
ser maior ou igual que o comprimento de seu terceiro lado. (Não resta dúvida
de que podemos aparecer com diversas outras restrições similares em Triângu-
lo.)
A grade tridimensional da Figura 10.6 (com os eixos rotulados como a, b
e c) representa o espaço-estado de Triângulo. Porém, isso não é estritamente
verdadeiro; trata-se de um exagero. Apenas alguns dos pontos na grade — os
quais satisfazem a invariante anterior para o Triângulo — fazem parte do real
espaço-estado de Triângulo. Por exemplo, não poderíamos ter um triângulo com
lados de 1, 2 e 5.

4. Para manter esse exemplo simples, ignorarei a posição e a orientação dos triângulos e me con-
centrarei no tamanho deles. A invariante (com seu ≥ em lugar de >) permite degenerar triân-
gulos que são só linhas retas.
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 273

Figura 10.6 O espaço-estado 3-D de Triângulo — mas nem


todas posições representando triângulos válidos.

A Figura 10.7 mostra uma hierarquia de variedades de triângulos.

Figura 10.7 Quatro variedades de triângulos.

A classe TriânguloIsósceles tem (dentro dos limites de igualdade para nú-


meros reais de computador) a invariante:

a = b or b = c or c = a

e a classe TriânguloRetângulo tem a invariante (devido ao falecido sr. Pi-


tágoras!):
274 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

a * a + b * b = c * c // assumo por simplificação que c é a hipotenusa.5

Pelo fato de as invariantes serem herdadas, uma dada classe deve


obedecer à(s) invariante(s) de sua(s) superclasse(s). Assim, já que tanto
TriânguloIsósceles quanto TriânguloRetângulo são subclasses de Triângulo,
elas também herdam a invariante

a + b ≥ c and b + c ≥ a and c + a ≥ b

Portanto, os objetos TriânguloIsósceles sempre obedecerão a esta invarian-


te integral:

(a + b ≥ c and b + c ≥ a and c + a ≥ b) and (a = b or b = c or c = a)

Para tentar acompanhar isso mais adiante, a classe TriânguloIsóscelesRe-


tângulo (que é herdeira tanto de TriânguloRetângulo quanto de TriânguloIsósce-
les) tem um espaço-estado constituído de pontos que são válidos para ambos,
TriânguloRetângulo e TriânguloIsósceles. Sua invariante de classe é, portanto, o
lógico and das invariantes de TriânguloRetângulo e TriânguloIsósceles,6 a saber:

(a + b ≥ c and b + c ≥ a and c + a ≥ b) and


a * a + b * b = c * c and
(a = b or b = c or c = a)

10.5 Precondições e Pós-condições


Até o momento, analisamos principalmente as regras — as invariantes — que
governem as classes como um todo. Agora vamos voltar às condições que go-
vernam as operações individuais.
Toda operação tem uma precondição e uma pós-condição. A precondição
é uma condição que deve ser verdadeira quando a operação começar a execu-
tar. Caso ela não seja verdadeira, então pode legitimamente recusar a execu-
tar e possivelmente dará origem a alguma condição de exceção. A pós-condição
é uma condição que deve ser verdadeira quando a operação finalizar sua exe-
cução. Se ela não for verdadeira, então a implementação da operação será de-
ficiente e deverá ser arrumada.

5. Veja o exercício 4 para detalhes adicionais.


6. Note que, nas linguagens como C++ e Java, a invariante de classe é uma construção teórica,
não diretamente implementada na linguagem. Nessas linguagens, as invariantes de classe,
portanto, não são herdadas da mesma forma que os atributos.
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 275

Tome como exemplo a operação Pilha.eliminar, que remove o elemento do


topo de uma pilha normal do tipo “o último item dentro, o primeiro fora”. A
precondição para essa operação é:

not vazia

Se a precondição de uma operação é satisfeita, a operação deve assegurar


que a pós-condição da mesma seja satisfeita quando a operação finalizar sua
execução. Por exemplo, a pós-condição para remover será

(númDeElementos = old númeroDeElementos — 1) and not completo

em que a palavra-chave old significa “qualquer que seja o valor que esta
tinha antes de a operação ser executada”.
Bertrand Meyer e outros descrevem as precondições e pós-condições de
uma operação como um contrato entre uma operação e um cliente que envia
uma mensagem para essa operação.7 A metáfora do contrato implica que

1. se o remetente da mensagem consegue garantir que a precondição é ver-


dadeira, então a operação destinatária garante que a pós-condição é ver-
dadeira após a execução;
2. se, por um outro lado, o remetente da mensagem não consegue garantir
que a precondição é verdadeira, então todo o acordo está acabado: a ope-
ração nem é obrigada a executar, e nem mesmo é obrigada a garantir a
pós-condição.

Lembre-se de que a invariante de classe é verdadeira quando uma ope-


ração começar a executar e quando ela terminar. Assim, a história completa
de pré e pós-condições é que uma operação é introduzida entre duas condições
compostas, como esta:

Invariante de classe and precondição de operação


Operação executa
Invariante de classe and pós-condição de operação

Como exemplo, vamos tomar a operação escalaHorizontal, definida na clas-


se Retângulo. A operação, que aparece na Figura 10.8, expande ou contrai a
largura de um retângulo por um fator de multiplicação e toma um argumento,
fatorDeEscala.

7. Veja, por exemplo, Meyer, 1988; Meyer, 1992, ou Wiener,1995.


276 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Figura 10.8 Retângulo.escalaHorizontal.

Vamos assumir que o retângulo tenha lados w1, h1, w2 e h2.8 Sua inva-
riante de classe será:
w1 = w2 and h1 = h2

Uma precondição em escalaHorizontal será:

larguraMáximaPermitida ≥ w1 * fatorDeEscala

A pós-condição óbvia será

w1 = old w1 * fatorDeEscala

Agrupando tudo junto, temos:


w1 = w2 and h1 = h2 and larguraMáximaPermitida ≥ w1 * fatorDeEscala
// precondição completa
escalaHorizontal (fatorDeEscala) // a operação
w1 = w2 and h1 = h2 and w1 = old w1 * fatorDeEscala
// pós-condição completa

Desnecessário dizer que poucas pessoas realmente escrevem a invariante


de classe como parte de toda pré e pós-condição; isso seria tedioso e redundan-
te. Entretanto, a invariante de classe lá está implicitamente, e você deveria
reverenciá-la ao desenhar quer uma operação ou um método que implemente
uma operação.

10.6 Resumo
Este capítulo introduziu as propriedades de uma classe: espaço-estado e com-
portamento permitido. O espaço-estado de uma classe A é a totalidade dos es-

8. Você talvez esteja se perguntando por que minha classe Retângulo tinha todos os quatro la-
dos de um retângulo, quando apenas largura e altura resolveriam. Eu fiz isso para prover um
simples exemplo de uma invariante de classe. Nas seções 13.1 e 13.2 examino, em detalhes,
diversos desenhos para Retângulo.
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 277

tados permitidos a um objeto da classe A. As dimensões de um espaço-estado


são as coordenadas necessárias para especificar o estado de um objeto. O com-
portamento permitido de A é o conjunto de transições no espaço-estado de A
que um objeto é capaz de efetuar legalmente.
Se B é uma subclasse de A, então o espaço-estado de B deve estar intei-
ramente confinado dentro do espaço-estado de A. Entretanto, o espaço-estado
de B pode estender-se a partir do espaço-estado de A, o que significa que ele
tem dimensões que faltam ao espaço-estado de A. De maneira similar, o com-
portamento de B pode ser tanto um confinamento quanto um prolongamento
do comportamento de A.
Todos os objetos de uma dada classe devem satisfazer a invariante de
classe. Essa invariante atua como uma restrição, limitando o tamanho do es-
paço-estado da classe. Uma subclasse herda a(s) invariante(s) de sua(s) super-
classe(s) e agrega uma restrição adicional que lhe é peculiar.
Cada operação de uma classe tem uma precondição e uma pós-condição,
que juntas formam um contrato entre a operação e um cliente dessa operação.
A precondição estabelece o que deve ser verdadeiro para a operação executar.
A pós-condição estabelece o que será verdadeiro quando a operação tiver com-
pletado a execução. Formalmente falando, a invariante de classe é parte das
pré e pós-condições de todas as operações. (Entretanto, na realidade, ninguém
escreve a invariante de classe como parte das pré e pós-condições.)
As invariantes de classe, juntamente com as precondições e pós-condi-
ções, formam uma estrutura para a abordagem de desenho conhecida como
“desenho por comprometimento, ou contrato”, que garante que uma operação
do objeto destinatário gerará a resposta adequada para uma mensagem, con-
tanto que o objeto cliente tenha obedecido à precondição dessa operação.
No próximo capítulo, utilizaremos espaços-estados, comportamento, inva-
riantes de classe, precondições e pós-condições, para edificarmos os importan-
tes princípios do desenho, denominados princípios da conformidade e do
comportamento fechado, e para avaliarmos a qualidade de nossas hierarquias
de classes.
278 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

10.7 Exercícios
1. Suponha uma classe ContaDeCartãoDeCrédito com duas dimensões (entre
outras) denominadas saldoAtual e limiteDeCrédito. Por que não nomeamos
simplesmente a dimensão saldoAtual após sua classe Dinheiro?
2. O que é a dimensionalidade do espaço-estado da classe Retângulo? Em ou-
tras palavras, quantas dimensões tem a classe Retângulo? Antes de você
responder: “Duas! Altura e largura. Isso é fácil —, deixe-me salientar que
a questão é indefinida até que você tenha uma boa idéia de qual é a abs-
tração realmente representada por Retângulo. Assim, deixe-me definir Re-
tângulo como uma classe cujos objetos são retângulos capazes de girar,
sofrer expansões na altura e largura e vagar pelo plano.
Portanto, quantas dimensões a classe Retângulo, com essa definição,
realmente tem? Em outras palavras, quantos graus de liberdade tem
realmente cada objeto Retângulo? Se você fosse desenhar a implementa-
ção interna de Retângulo, quantos graus de liberdade seu desenho interno
permitiria? Esse número poderia ser diferente em sua primeira resposta?
Em caso afirmativo, qual a razão?
3. Como o comportamento de uma subclasse poderia ser considerado mais
restrito (com relação a sua superclasse) se uma subclasse tivesse dimen-
sões extras em seu espaço-estado, tendo-se em vista os espaços-estados de
suas superclasses?
4. As invariantes de classe para as classes TriânguloRetângulo, TriânguloIsós-
celes e TriânguloIsóscelesRetângulo eram complexas e ainda similares
umas às outras. Você poderia decompor essas similaridades de alguma
forma, e, portanto, também simplificar a expressão das invariantes?

10.8 Respostas
1. Embora saldoAtual possa pertencer à classe Dinheiro, nomear a dimensão
saldoAtual como Dinheiro causaria certa ambigüidade. Por exemplo, visto
que pelo menos duas dimensões do espaço-estado de ContaDeCartãoDeCré-
dito são monetárias, como saberíamos que dimensão era o saldoAtual e
que dimensão representaria o limiteDeCrédito?
Incidentalmente, poderíamos optar por conferir a saldoAtual uma clas-
se mais sofisticada, digamos SaldoDeCartão (a qual se basearia na classe
mais fundamental Dinheiro). Isso poderia constituir uma solução extrava-
gante, porque ela nos proporcionaria outra classe em nossa biblioteca re-
querendo manutenção. Apenas seria recompensador atuar dessa forma se
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 279

SaldoDeCartão dispusesse de propriedades de negócio complicadas ou im-


portantes.
2. Eu responderei às questões neste exercício na ordem inversa. Em primei-
ro lugar, vamos explorar as três possibilidades de abordagem sobre o de-
senho interno de Retângulo e estimar a dimensionalidade (ou graus de
liberdade) de cada uma delas. Cada abordagem de desenho — eu as de-
nominarei de A, B e C — aparentemente vai prover uma dimensionali-
dade diferente! Após examinarmos cada abordagem, resolverei esse
aparente conflito por meio do conceito de invariante de classe a fim de
propor a verdadeira dimensionalidade de Retângulo.
Abordagem A: Considere cada objeto Retângulo como represen-
tado por quatro pontos em um plano. Pelo fato de cada ponto ter
duas dimensões (x e y), o espaço-estado de Retângulo terá oito di-
mensões (ou seja, 4 multiplicado por 2).
Abordagem B: Considere um objeto Retângulo como representa-
do por apenas dois pontos: um ponto superiorEsquerdo e um pon-
to inferiorDireito. Embora isso possa aparentar conferir ao
espaço-estado somente quatro dimensões, não é o bastante. Dois
pontos funcionarão para retângulos horizontais, mas eles são
ambíguos para retângulos capazes de girar. (Tente isso com um
lápis e papel.) Nós, portanto, também precisamos conhecer o ân-
gulo de orientação do retângulo. Essa abordagem propicia cinco
dimensões (ou seja, 2 mais 2 mais 1).
Abordagem C: Podemos igualmente especificar um objeto Retân-
gulo, definindo seu centro, sua altura, sua largura e sua orienta-
ção. Isso também vai prover um espaço-estado em cinco
dimensões (ou seja, 2 mais 1 mais 1 mais 1).
Assim, qual deverá ser a resposta? Será que Retângulo tem um espaço-
estado em oito ou cinco dimensões? A resposta é: o mínimo de todos esses va-
lores — em outras palavras, cinco dimensões.
Mas, por que o mínimo? Para responder a essa questão, vamos examinar
cada uma das três abordagens para ver como a invariante de classe reduz os
graus de liberdade em um desenho interno de Retângulo que parece ter mais
de cinco dimensões.
Abordagem A: Embora quatro pontos sobre um plano certamen-
te especifiquem um retângulo, também podem especificar uma
grande quantidade de formatos não retangulares, tais como tra-
280 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

pezóides e paralelogramos. Dessa forma, se escolhermos quatro


pontos como as variáveis de instância que definem um retângu-
lo, em decorrência teremos de estabelecer uma invariante de
classe para Retângulo que proibirá formatos não retangulares.
Os cálculos seguintes mostram-nos quanto trabalho teremos
para impor uma restrição como essa.
Quatro pontos têm oito graus de liberdade, mas os retângulos
(conforme vimos) podem ser desenhados para ter só cinco graus.
Por conseguinte, se optarmos pela abordagem A como nosso de-
senho interno de Retângulo, necessitaremos escrever e impor
uma invariante de classe para retornar a dimensionalidade da
classe efetivamente de volta para cinco. Essa invariante de clas-
se deve conter três (8 menos 5) restrições. O seu conhecimento
do tema sobre retângulos lhe inspirará, proporcionando a você
várias maneiras de escrever essa invariante.
Por exemplo, você poderá unir os pontos com linhas e verificar
que três interseções são ângulos retos. Ou você poderá verificar
que uma interseção é um ângulo reto e que as linhas formam
dois pares de comprimentos iguais. Ou você poderá eliminar in-
teiramente um dos pontos e verificar que os três pontos rema-
nescentes satisfazem a condição de Pitágoras. Quando você
visualizar o trabalho necessário para escrever a invariante de
classe para a abordagem A, talvez se pergunte se poderia encon-
trar uma representação central mais simples, cuja dimensiona-
lidade fosse mais próxima de cinco e que, portanto, simplificaria
a invariante de classe.
Abordagens B e C: As invariantes de classe serão simples para
esses desenhos internos, porque as variáveis de instância de Re-
tângulo em conjunto têm cinco dimensões (graus de liberdade)
— exatamente o mesmo número como vimos para retângulos
como um todo. Por exemplo, na abordagem C, centro, altura, lar-
gura e orientação, muito dificilmente não representariam um re-
tângulo válido! Não importa os valores que você coloque nessas
variáveis, você obterá um retângulo. (Na verdade, você tem de
assegurar que altura e largura sejam não negativos).
Visto que elas são independentes umas das outras, essas quatro
variáveis (centro, altura, largura e orientação) provêm ao espaço-
Cap. 10 ESPAÇO-ESTADO E COMPORTAMENTO 281

estado de Retângulo cinco dimensões fundamentais (2 mais 1


mais 1 mais 1). Todos os outros conjuntos de variáveis de ins-
tância representativos de retângulos também provêm, pelo me-
nos, cinco dimensões fundamentais. (Tente algumas!)
Assim, o que está realmente acontecendo aqui? A resposta é que Retân-
gulo tem duas dimensionalidades, uma externa e a outra interna. A di-
mensionalidade externa de Retângulo é a sua dimensionalidade como um
tipo, e é sempre cinco. A dimensionalidade interna de Retângulo é a sua
dimensionalidade como uma classe (uma implementação de um tipo).
Esta dimensionalidade deve ser, no mínimo, cinco. A diferença entre a di-
mensionalidade interna e a externa é igual ao número de restrições que
devemos aplicar por meio da invariante de classe para assegurar que o
desenho de classe cumpra os requisitos do tipo, e nada mais a não ser o
tipo. (No Capítulo 13, retornamos a Retângulo para explorar os seus de-
senhos interno e externo mais um pouco).
Finalmente, note que você pode agrupar dimensões simples em di-
mensões de maior complexidade de diversas formas, sem mudar a dimen-
sionalidade. Por exemplo, na abordagem B, vimos as cinco dimensões de
Retângulo agrupadas em três dimensões: dois pontos e um ângulo. Duas
dessas três dimensões (os pontos) são da classe Ponto, que tem duas di-
mensões fundamentais (ambas da classe NúmeroReal). Formar abstrações
de nível mais alto (tais como Ponto, em lugar de meramente duas classes
NúmeroReal) é geralmente uma boa prática de desenho orientado a objeto
(como indiquei na seção 9.2.2, sobre o grau de dependência).
3. O comportamento que uma subclasse herda de uma superclasse deve ope-
rar dentro do espaço-estado possivelmente restrito da subclasse. Portan-
to, a subclasse talvez tenha de suprimir a definição de uma operação
herdada com uma versão de menor alcance. Todavia, uma subclasse é li-
vre para agregar qualquer comportamento para o comportamento herda-
do, contanto que a invariante de classe seja respeitada.
Como analogia, considere o tenor Luciano Pavarotti como um objeto
de uma superclasse e eu como um objeto de uma subclasse. Também sei
cantar, mas o meu volume, alcance e profundidade de expressão são mui-
to menores do que os do mestre Pavarotti. Suponha igualmente que eu
saiba todas as canções conhecidas por ele, mas, infelizmente, minha im-
plementação das mesmas não seja tão extraordinária. De fato, há algu-
mas árias que eu absolutamente posso me negar a cantar, porque o
alcance delas está além de minhas possibilidades. Em contrapartida, can-
282 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

to algumas de suas melodias com trechos extras de tremulação de minha


própria autoria.
Não obstante o estipulado, existem diversos números de rock que o
mestre Pavarotti desconhece, mas que têm sido muito afortunados para
mim (por exemplo, “TheYing Tong Song” e a canção country favorita de
todos os tempos “Baby, I Wanna Drive You Wild, But I Can’t Get Out of
First Gear”). Dessa forma, embora eu tenha herdado uma coleção de can-
ções do mestre Pavarotti cuja implementação modifiquei e restringi ao
meu próprio talento, igualmente ampliei o repertório com algumas músi-
cas que me apanho cantando em uma dimensão inteiramente nova.
4. Invariantes de classe complexas freqüentemente possuem subexpressões
que podem ser decompostas como funções booleanas. Por exemplo, a pro-
priedade de Pitágoras quanto a um triângulo retângulo não pode ser su-
cintamente expressa devido a dois motivos. Primeiro, não é óbvio quais
dos três lados é a hipotenusa. Segundo, em razão dos arredondamentos
de números reais, a restrição pitagoreana jamais poderá ser expressa
como uma igualdade exata. Entretanto, você pode escrever uma função
booleana:

sePitagoreana (a, b, c: NúmeroReal) Booleano:

que pode ser integrante da invariante de classe de, digamos, TriânguloRe-


tângulo e TriânguloIsóscelesRetângulo e que cuidará de complexidades ino-
portunas.
C onformidade de tipo e
comportamento
fechado
FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML
CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO
11.Conformidade de Tipo e Comportamento Fechado

U ma hierarquia de classes robusta é apropriada para serviço contínuo e


prolongado, e para obter satisfação para sempre: os objetos de uma clas-
se, felizmente, vão substituir objetos de uma superclasse, e a hierarquia como
um todo resistirá às provações do tempo. Em contrapartida, uma hierarquia
de classes miseravelmente desenhada pode constituir um problema e provocar
um final prematuro do projeto e da orientação a objeto em seu local de tra-
balho.
Classes, classes, por toda a parte.
Mais do que você possa ter imaginado.
Classes, classes, por toda a parte.
Tampouco quaisquer duas classes se teriam ligado.
Este capítulo introduz alguns princípios vitais à construção de hierar-
quias de classes vigorosas e saudáveis. Os princípios são a conformidade de
tipo e o comportamento fechado.
Na primeira seção deste capítulo, examino novamente e contrasto as no-
ções de classe e tipo, dois termos analisados anteriormente neste livro. Na se-
gunda seção, introduzo o princípio de conformidade como uma forma de
entender a verdadeira natureza dos subtipos. Eu recomendo que, para se evi-
tar surpresas desagradáveis, as hierarquias de classes sejam construídas ba-
seadas em hierarquias saudáveis.
Entender subtipos em sistemas orientados a objeto é complicado devido
à interação das três maiores sofisticações da orientação a objeto: encapsula-
mento de nível-2, herança e objetos utilizados como argumentos de mensa-
gens. Felizmente, entretanto, podemos recorrer às idéias de precondições,
pós-condições e invariantes de classe do Capítulo 10 para ajudar no esclareci-

283
284 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

mento dos temas eventualmente complicados que se movem furtivamente pe-


los subtipos.
A terceira seção do capítulo prossegue no tópico de subclasses vigorosas
com o importante princípio do comportamento fechado. Nessa seção, também
mostro como os dois princípios, de conformidade de tipo e comportamento fe-
chado, muitas vezes limitam ou modificam os desenhos de hierarquias de clas-
ses robustas.

11.1 Classe versus Tipo


A melhor forma de pensar em uma classe é como a implementação de um tipo,
que é a visão abstrata ou externa de uma classe. Em outras palavras, o tipo
inclui a finalidade da classe, juntamente com seu espaço-estado e comporta-
mento. (Especificamente, o tipo de uma classe é definido pelo seguinte: a fina-
lidade da classe; a invariante da classe; os atributos da classe; as operações
da classe; e as precondições, pós-condições, definições e assinaturas das ope-
rações.)
Uma classe em seu todo, entretanto, vincula um desenho interno que im-
plementa características externas como um tipo. O desenho interno, conforme
visto no Capítulo 1, inclui o desenho das variáveis da classe e o desenho dos
algoritmos para os métodos das operações.
Na verdade, um único tipo pode ser implementado sob a forma de diver-
sas classes, com cada classe tendo seu próprio e particular desenho interno.
Por exemplo, você pode criar diversas classes do mesmo tipo a fim de prover
a cada classe sua própria primazia de eficiência sob alguma circunstância es-
pecial. Um desenho de GrupoOrdenado pode, possivelmente, ter passagens
muito eficientes, enquanto outro pode ter inserções e supressões, de modo
idêntico, muito eficientes. No entanto, ambas as versões de GrupoOrdenado po-
dem implementar o mesmo tipo com os mesmos atributos e operações. De ma-
neira similar, você pode desenhar uma versão especial de uma classe para
utilizar um algoritmo que execute muito rapidamente em determinado modelo
de computador.
A Figura 11.1 mostra a notação da UML para um tipo, a qual é idêntica
ao símbolo de classe mas tem o estereótipo «type» no retângulo do nome.
Quando aparecerem tipos no diagrama, talvez você prefira utilizar o estereó-
tipo «class» nos símbolos de classes para enfaticamente distingui-las dos tipos;
como fiz na Figura 11.1.
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 285

Figura 11.1 O tipo Grupo com seu subtipo Árvore, o qual


é implementado pelas duas classes.

As setas tracejadas marcadas com o estereótipo «implements» indicam


como distinguir qual classe é uma implementação de qual tipo. Conforme mos-
tra a Figura 11.1, um único tipo pode ser implementado por várias classes,
cada qual provavelmente com suas próprias características de eficiência. (Nor-
malmente, essas classes não teriam nomes tão extensos como esses; suas téc-
nicas de implementação muito provavelmente apareceriam na documentação
interna delas.)
A seta à esquerda mostra que o tipo Árvore é um subtipo de Grupo. O fato
de Grupo também ser {abstrato} significa que Grupo não necessita ser imple-
mentado por quaisquer classes; apenas os subtipos de Grupo vão requerer
classes implementadoras.
Embora um tipo represente a visão externa de uma classe, o conceito de
subtipo é distinto do conceito de subclasse. Até mesmo sem uma definição for-
mal de subtipo (que aparece na próxima seção), você provavelmente irá dizer
intuitivamente que as subclasses do parágrafo seguinte não constituem sub-
tipos válidos de suas respectivas superclasses.
Em razão de ser possível fazer com que uma classe seja, sintaticamente,
uma subclasse de qualquer outra classe, você poderia fazer algo como Elefante
é herdeiro de Retângulo ou RepresentanteDeVendas é herdeiro de Tijolo. Essas
286 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

são estruturas classe/subclasse válidas, mas semanticamente elas não fazem


sentido porque as subclasses não apresentam qualquer tipo de relação com
suas respectivas superclasses.
Por exemplo, não faria qualquer sentido aplicar o teorema de Pitágoras
em um elefante a fim de encontrar sua diagonal! E uma operação lançarComis-
são não estaria feliz de obter um material de construção como argumento
quando ela estivesse, na realidade, esperando por um representante de ven-
das. (A maioria dos clientes também não ficaria satisfeita, embora eu admita
que, às vezes, é difícil distinguir certos representantes de vendas de grandes
blocos de material de construção.)
Dessa forma, mesmo S sendo uma subclasse de T, essa condição não acar-
reta automaticamente que S também seja um subtipo de T.1 Você tem de tra-
balhar no projeto da classe S se você quiser fazer dela um subtipo de T. O
restante deste capítulo explica o que significa esse trabalho: os princípios aos
quais S deverá obedecer para se fazer da mesma um verdadeiro subtipo de sua
superclasse T.

11.2 O Princípio da Conformidade de Tipo


O princípio de desenho da conformidade de tipo é proveniente da teoria de ti-
pos de dado abstratos, na qual se baseia a orientação à objeto. Esse princípio,
que é extremamente importante na criação de hierarquias de classes forma-
doras de biblioteca de classes, estabelece que

Se S é um real subtipo de T, então S deve se conformar a T. Em ou-


tras palavras, um objeto de tipo S pode ser provido em qualquer con-
texto no qual um objeto de tipo T seja esperado, e a exatidão ainda
será preservada quando qualquer operação de acesso do objeto for
executada.2

Por exemplo, Círculo é um subtipo de Elipse. Qualquer objeto que for um


círculo também será uma elipse — se bem que uma elipse muito redonda. As-

1. Incidentalmente, um subtipo, de modo idêntico, não necessita ser uma subclasse. Mesmo em
uma linguagem isenta de uma hierarquia de herança do tipo superclasse/subclasse, você ain-
da pode desenhar e implementar subtipos à mão (por meio de código tediosamente duplicado).
Entretanto, deve-se obedecer ao princípio da conformidade de tipo, o qual corresponde ao tó-
pico central da próxima seção.
2. Para mais detalhes sobre o princípio da conformidade de tipo em um meio orientado a objeto,
veja Meyer, 1992 e LaLonde e Pugh, 1991. O princípio da conformidade de tipo é muito simi-
lar ao que autores tais como Barbara Liskov denominam princípio da substitutibilidade (Lis-
kov e outros, 1981).
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 287

sim, qualquer operação que estiver esperando receber uma elipse como um ar-
gumento em uma mensagem deverá estar muito feliz por obter um círculo.3
Embora tivéssemos visto na seção 11.1 que subclasse e subtipo são con-
ceitos distintos, estou agora torcendo minhas palavras e dizendo:

Em um desenho orientado a objeto sadio, o tipo de cada classe deve-


rá conformar-se ao tipo de sua superclasse. Em outras palavras, a
hierarquia de herança subclasse/classe deverá seguir o princípio da
conformidade de tipo.

A razão para isso é que, para explorarmos com comodidade o polimorfis-


mo, deveríamos ser capazes de passar os objetos de uma subclasse em lugar
dos objetos de uma superclasse. Mas como assegurar que o tipo de cada sub-
classe verdadeira e honestamente se conforma com o tipo de sua superclasse?
Para responder a essa questão, introduzo na próxima seção dois importantes
subprincípios da conformidade, contravariação e covariação. Esses princípios
utilizam as noções que abordamos no capítulo anterior: invariantes de classe,
precondições e pós-condições, espaço-estado e comportamento.

11.2.1 Os princípios da contravariação e covariação

AVISO QUANTO À SUA SAÚDE MENTAL: Os conceitos presentes


nesta seção são importantes, mas vão contra a intuição em uma pri-
meira leitura. Na primeira vez em que me deparei com eles, tive de
refletir muito antes do café da manhã para que eles começassem a fa-
zer sentido. Portanto, esteja preparado para ler esta seção pelo me-
nos duas vezes.

Para assegurar conformidade de tipo em uma subclasse, é preciso em pri-


meiro lugar, assegurar que a invariante da subclasse seja, no mínimo, tão for-
te quanto a da superclasse. Por exemplo, Retângulo tem a invariante w1 = w2
and h1 = h2. Quadrado tem a invariante w1 = w2 and h1 = h2 and w1 = h1.
Até aí tudo bem, porque a invariante de Quadrado é mais forte do que a inva-
riante de Retângulo. (Em outras palavras, um objeto que satisfaça a invariante
de Quadrado está fadado a satisfazer a invariante de Retângulo; um objeto que

3. Naturalmente, isso não funciona se a operação tentar ampliar o círculo! Isso explica o aviso
“quando qualquer operação de acesso for executada” (que implica que nenhum objeto muda
de estado) em minha definição de conformidade. Dificuldades como essa transmitem um bo-
cado de incitamento, conforme discuto na seção 11.3.
288 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

satisfaça a invariante de Retângulo talvez não satisfaça a invariante de Qua-


drado.)
Em segundo lugar, é preciso assegurar que, em suas operações, sejam
cumpridas as três restrições a seguir:

1. Toda operação da superclasse deve ter uma correspondente operação na


subclasse com o mesmo nome e assinatura.
2. A precondição de qualquer operação não deve ser mais forte do que a ope-
ração correspondente na superclasse. Isso é conhecido como o princípio
da contravariação, assim denominado porque a resistência das precondi-
ções da operação na subclasse caminha no sentido oposto à resistência da
invariante de classe. Ou seja, as precondições das operações ficam, caso
existam, mais fracas.4
3. A pós-condição de qualquer operação deve ser, no mínimo, tão forte quan-
to a operação correspondente na superclasse. Isso é conhecido como o
princípio da covariação, assim denominado porque a resistência das pós-
condições da operação na subclasse caminha no mesmo sentido da resis-
tência da invariante de classe. Ou seja, as pós-condições das operações
ficam, caso existirem, mais fortes.
Essas restrições são facilmente satisfeitas se uma subclasse herda pelo
menos uma operação de sua superclasse. Nesse caso, o nome e a assinatura,
assim como as precondições e pós-condições, são idênticas tanto na superclas-
se como na subclasse. Surgem temas mais interessantes quando a subclasse
omite a operação de uma superclasse com uma operação exclusivamente dela,
como no exemplo seguinte.

11.2.2 Um exemplo de contravariação e covariação


A classe Funcionário tem uma subclasse Gerente. (Sim, os gerentes fazem parte
de uma subclasse de funcionários!) O que devemos fazer para assegurar que
Gerente seja um subtipo válido de Funcionário?
Primeiro, digamos que uma invariante de Funcionário é nívelDeFormação
> 0, e uma invariante de Gerente é nívelDeFormação > 20. Isso faz com que a
invariante da classe Gerente seja mais forte do que a invariante da classe Fun-
cionário; e, dessa forma, estamos indo bem nesse ponto.

4. Os termos mais forte e mais fraco nas restrições acima não descrevem de maneira alguma
qualidade ou robustez. Mais forte não é melhor e mais fraco não quer dizer pior.
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 289

Segundo, vamos considerar calcularBônus uma operação de Funcionário.


Essa operação toma avaliaçãoDeDesempenho (avaliação de performance) e cal-
cula %DeBônus (porcentagem de bônus), a qual constitui uma porcentagem do
salário normal dos funcionários. A UML da Figura 11.2 mostra a assinatura
da operação calcularBônus.

Figura 11.2 Operação calcularBônus definida em Funcionário e Gerente.

Diremos, por simplicidade, que avaliaçãoDeDesempenho (o argumento de en-


trada de dados passado para calcularBônus) é um número inteiro entre 0 e +5. O
argumento de saída de dados, %DeBônus, está compreendido entre 0% e 10%.
Os algoritmos para cálculo dos bônus podem ser diferentes para gerentes
e não gerentes. Portanto, a classe Gerente pode omitir calcularBônus com uma
operação exclusivamente dela (com o mesmo nome e assinatura).
Assim, vamos retornar à operação calcularBônus conforme definida na
classe Gerente. Lembre-se de que para Gerente conformar-se a Funcionário, Ge-
rente.calcularBônus deverá ter uma precondição igual ou mais fraca do que Fun-
cionário.calcularBônus. Isso significa, em particular, que a faixa do argumento
de entrada, avaliaçãoDeDesempenho, de Gerente.calcularBônus deverá ser igual
ou maior do que a faixa do argumento de entrada, avaliaçãoDeDesempenho, de
Funcionário.calcularBônus. (Para ajudar-me a lembrar que “faixa maior = con-
dição mais fraca”, penso em “maior” como “mais solta”, e em “menor” como
“mais restrita”.)
Portanto, cada uma das faixas seguintes para o argumento de entrada
avaliaçãoDeDesempenho de Gerente.calcularBônus seria válida:
290 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

0a5 // igual em ambas as classes Gerente e Funcionário


0a8 // maior (mais fraca) em Gerente
-1 a 9 // maior (mais fraca) em Gerente (assumir uma avaliação
// negativa faz sentido!)
De maneira contrária, as faixas seguintes para o argumento de entrada
avaliaçãoDeDesempenho de Gerente.calcularBônus seriam ilegais:

1a5 // menor (mais forte) em Gerente


2a4 // até mesmo menor (mais forte) em Gerente

A Figura 11.3 mostra as faixas válidas e ilegais para avaliaçãoDeDesem-


penho sob forma gráfica.

Figura 11.3 Contravariação: possíveis faixas para avaliaçãoDeDesempenho em


Gerente.calcularBônus (avaliaçãoDeDesempenho, out %DeBônus).

Para Gerente conformar-se a Funcionário, Gerente.calcularBônus deverá ter


uma pós-condição igual ou mais forte do que Funcionário.calcularBônus. Isso
significa, em particular, que a faixa do argumento de saída, %DeBônus, de Ge-
rente.calcularBônus deverá ser igual ou menor do que a faixa do argumento de
saída, %DeBônus, de Funcionário.calcularBônus.
Portanto, cada uma das faixas seguintes para o argumento de saída %De-
Bônus de Gerente.calcularBônus seria válida:
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 291

0% a 10% // igual em ambas as classes Gerente e Funcionário


0% a 6% // menor (mais forte) em Gerente
2% a 4% // até mesmo menor (mais forte) em Gerente

De maneira contrária, cada uma das faixas seguintes para o argumento


de saída, %DeBônus, de Gerente.calcularBônus seriam ilegais:

0% a 12% // maior (mais fraca) em Gerente


–1% a 13% // maior (mais fraca) em Gerente (assumir um bônus
// negativo faz sentido!)

A Figura 11.4 mostra as faixas válidas e ilegais para %DeBônus sob forma
gráfica.

Figura 11.4 Covariação: faixas possíveis para %DeBônus em


Gerente.calcularBônus (avaliaçãoDeDesempenho, out %DeBônus).

Os princípios de precondições mais fracas e pós-condições mais fortes em


operações de subclasses não são intuitivos (é o mínimo que se pode dizer!). Na
realidade, quando os apresento em palestras, deixo o motor de meu carro fun-
cionando no caso de necessitar dar uma rápida escapada de alguns alunos en-
furecidos. Conseqüentemente, acho que deveria explicar com mais detalhes
por que esses princípios serão importantes para você na qualidade de dese-
nhista orientado a objeto quando desejar que sua hierarquia de classes siga
uma verdadeira hierarquia de tipo.
292 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

A principal importância da conformidade de tipo origina-se das deman-


das do desenho de segunda ordem, no qual um argumento em um sistema
orientado a objeto, por ser um objeto, transporta “todo o código genético de sua
classe” junto com ele.5 Por exemplo, vamos supor que nós passemos objetos,
um de cada vez e representando funcionários, a uma operação conduzirAvalia-
çãoDeFinalDeAno (definida, digamos, na classe Departamento), que realiza uma
avaliação de final de ano para cada funcionário. Essa operação, conduzirAva-
liaçãoDeFinalDeAno, calcula um valor para avaliaçãoDeDesempenho (a avaliação
do desempenho do funcionário), e então envia uma mensagem ao objeto refe-
renciado por funcionário para executar a operação calcularBônus do objeto.
Textualmente, esta mensagem seria funcionário.calcularBônus (avaliação-
DeDesempenho, out %Bônus). Eu mostro-a na notação da UML na Figura
11.5.6

Figura 11.5 ConduzirAvaliaçãoDeFinalDeAno invocando calcularBônus em


cada objeto funcionário. (Um determinado funcionário pode ser
tanto da classe Funcionário como da sua subclasse, Gerente.)

Quando a operação conduzirAvaliaçãoDeFinalDeAno apanha cada objeto (o


objeto referenciado por funcionário) da coleção de funcionários (referenciados
por funcionário), toda essa operação sabe que isso se trata de alguma espécie
de funcionário, comum ou gerente. Porém, conduzirAvaliaçãoDeFinalDeAno não
se preocupa com que espécie de funcionário ela tem; a operação sabe que, por
meio do milagre do polimorfismo, quando o objeto funcionário recebe a men-

5. Desenhos de segunda ordem surgem quando argumentos de mensagens têm encapsulamento


de nível-2 — em outras palavras, quando os argumentos de mensagens são (referências a) ob-
jetos. No desenho de primeira ordem, os componentes de software se comunicam passando ar-
gumentos com encapsulamento de nível-1 (funções ou procedimentos). Em desenho de
zerésima ordem, os argumentos são simplesmente dados. (O desenho estruturado é principal-
mente desenho de “zerésima ordem”, com algum desenho de primeira ordem ocorrendo onde
procedimentos são passados como argumentos.)
6. Comentário sobre notação: a mensagem é enviada de forma interativa para cada funcionário
em uma listagem de funcionários de um departamento. Os parênteses em torno de Funcio-
nário significam que o polimorfismo está em atividade e que o remetente da mensagem não
sabe que versão de calcularBônus será utilizada para satisfazer a mensagem cada vez que
esta foi enviada.
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 293

sagem funcionário.calcularBônus (avaliaçãoDeDesempenho, out %DeBônus), o ob-


jeto responde corretamente.
A operação conduzirAvaliaçãoDeFinalDeAno igualmente conhece a precon-
dição de calcularBônus, a regra para utilizar calcularBônus corretamente: con-
tanto que avaliaçãoDeDesempenho esteja entre 0 e 5, calcularBônus trabalhará
perfeitamente. Mas vamos supor que tivéssemos violado a conformidade de
tipo, pelo “fortalecimento” da faixa aceitável para o argumento de entrada de
Gerente.calcularBônus, avaliaçãoDeDesempenho, de 2 a 4. Por conseguinte, se
conduzirAvaliaçãoDeFinalDeAno inocentemente enviar avaliaçãoDeDesempenho
= 1 a um objeto, que por casualidade seja um gerente, então calcularBônus in-
terromperá (blow up), reclamando que a avaliaçãoDeDesempenho passada está
fora dos limites de variação.
Em contrapartida, se tivéssemos seguido o princípio de conformidade de
tipo, e “enfraquecido” a faixa aceitável para o argumento de saída de Geren-
te.calcularBônus, avaliaçãoDeDesempenho, de 0 a 8, não teríamos tido qualquer
problema. Dessa forma, não importaria qual o Gerente.calcularBônus fosse re-
cebido para avaliaçãoDeDesempenho na faixa de 0 a 5 — ele poderia ser mani-
pulado sem problema.
O inverso aplica-se à pós-condição. Nesse caso, se tivéssemos violado o
princípio da conformidade de pós-condições, seria conduzirAvaliaçãoDeFinalDeA-
no que iria sofrer. Digamos que nós enfraqueçamos a faixa aceitável para o ar-
gumento de saída, %DeBônus, para os limites de –1% a 12%. Uma vez que a
faixa para funcionários é supostamente de 0% a 10%, a operação conduzirAva-
liaçãoDeFinalDeAno seria tomada completamente de surpresa ao obter um nú-
mero negativo retornado para a mesma. Poderia até mesmo ficar tão surpresa
que ela interromperia totalmente.7

11.2.3 Uma ilustração gráfica de contravariação e


covariação
Visto que os princípios de contravariação e covariação podem parecer impene-
tráveis às pessoas que somente os encontram por meio de definições textuais,
tenho pensado comigo há bastante tempo em como apresentar os princípios
sob uma forma mais gráfica e imediata.
Na verdade, pensei em algo muito especial que aconteceu recentemente
enquanto regava meu jardim, na minha casa perto de Seattle. (Acredite ou
não, os nossos verões são normalmente quentes e secos.) Quando eu estava

7. Para mais ramificações deste exemplo funcionário/gerente, veja o exercício 1 no final do capí-
tulo.
294 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

tentando esticar a mangueira para alcançar uma árvore mais distante, notei
que meu vizinho também estava regando a parte correspondente ao terreno
dele.
“Saudações, amigo esguichador!” — anunciei cordialmente por sobre nos-
sa cerca tosca comum.
“O que você quer desta vez”, ele retrucou, com seu jeito amistoso habi-
tual. Ele já parecia saber o que eu desejava, pois recordava a última vez que
me dirigira a ele, e pedindo emprestado seu cortador de grama. (Aquilo tinha
sido um verdadeiro “golpe de misericórdia”.)

Meu vizinho, o regador.

Após uma breve negociação, tomei emprestada sua mangueira para es-
tender meu insuficiente tubo de borracha até o canto do jardim. Infelizmente,
entretanto, sua mangueira tinha um diâmetro não padronizado de 5/8 polega-
das, ao passo que a minha era de 3/4 polegadas — um calibre normal. Por sor-
te, eu tinha adquirido recentemente na loja de ferragens local, da HoseHut,
um conjunto de adaptadores para tubos apropriado para viagens ao exterior.
Após voltar correndo do barracão de ferramentas, acoplei as duas mangueiras
e rapidamente terminei de jogar água em minha plantação de cenouras.
Minha experiência em conectar mangueiras inspirou-me para que eu de-
senhasse a ilustração da Figura 11.6:
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 295

Figura 11.6 A invocação da operação op no supertipo T.

Utilizando a metáfora do conector de mangueira de escoamento de água,


essa figura ilustra a conexão de uma operação invocante i com uma operação
invocada op definida em uma classe T. À esquerda (o lado da entrada de da-
dos), vemos os argumentos reais de entrada da operação i para a operação in-
vocada op sendo despejados no tubo dos argumentos formais de op. Contanto
que a faixa de todos os argumentos reais provenientes de i se enquadre dentro
da faixa de seus correspondentes argumentos formais em op, os argumentos
de entrada serão admissíveis em op. Para simbolizar esse fato, desenhei o
tubo dos argumentos formais de entrada de op maior do que o tubo dos argu-
mentos reais de entrada da operação i.
À direita (o lado da saída de dados), temos uma situação oposta, quando
a operação invocada op retorna seus resultados. (Para fins de clareza, repeti
o nome i na extremidade direita.) Nesse caso, a faixa de cada argumento for-
mal de saída deverá cair dentro da faixa aceitável dos correspondentes argu-
mentos reais em i. Para simbolizar este fato, desenhei o tubo dos argumentos
formais de saída de op menor do que o tubo dos argumentos reais de saída da
operação i.
Agora, vamos examinar a Figura 11.7. Nesse caso, mostro uma outra ope-
ração op, dessa vez definida sobre S, que é uma subclasse de T. Em outras pa-
lavras, S.op omite e redefine T.op. Se S está prestes a ser um subtipo
verdadeiro de T, então as operações de S, tais como op, deverão obedecer aos
princípios da contravariação e covariação. Simbolizei o princípio de contrava-
riação desenhando o tubo dos argumentos formais de entrada de S.op (no lado
esquerdo) até mesmo maior do que era o tubo dos argumentos formais de en-
trada de T.op. Assim, se o tubo dos argumentos formais de entrada de T.op for
grande o bastante para aceitar os argumentos reais da operação i, então o
tubo de S.op será certamente, de maneira idêntica, suficientemente grande.8

8. Para tornar essa descrição bem compreensível, usei o conceito de uma faixa mais ampla, em
lugar do conceito mais geral de supertipo com ilustração de uma precondição mais fraca.
296 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Figura 11.7 A invocação da operação op no subtipo S.

À direita (o lado da saída de dados), novamente temos uma situação opos-


ta. Nesse caso, simbolizei o princípio de covariação desenhando o tubo dos ar-
gumentos formais de saída de S.op até mesmo menor do que era o tubo dos
argumentos formais de saída de T.op. Assim, se o tubo dos argumentos for-
mais de saída de T.op for pequeno o bastante para acoplar os argumentos reais
da operação i, então o tubo de S.op será certamente, de maneira idêntica, su-
ficientemente pequeno.
As duas figuras anteriores ilustram a natureza criteriosa dos princípios,
de outra forma obscuros, da contravariação e covariação: para que um subtipo
se conforme — ou seja, para que ele substitua seu supertipo —, um objeto do
subtipo deverá ser admissível sempre que um objeto do supertipo tiver sido
anteriormente aceito.

11.2.4 Resumo dos requisitos para conformidade de tipo


O princípio da conformidade de tipo implica que, para uma subclasse S ser um
subtipo verdadeiro de uma classe T, as seis restrições seguintes deverão ser
mantidas. (As duas primeiras aplicam-se às classes como um todo; as quatro
últimas aplicam-se às operações individuais.)

1. O espaço-estado de S deverá ter as mesmas dimensões que as de T. (Mas


S poderá ter dimensões extras que se estendam a partir do espaço-estado
de T).
2. Nas dimensões que S e T compartilham, o espaço-estado de S deverá ser
igual a (ou residir dentro do espaço-estado de) T. (Outra forma de dizer
isso: a invariante de classe de S deverá ser igual ou mais forte que a in-
variante de classe de T.)
Para cada operação de T (digamos, T.op) que S suprima e redefina com
S.op:
3. S.op deverá ter o mesmo nome que T.op.
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 297

4. A lista de argumentos da assinatura formal de S.op deverá corresponder


à listagem de argumentos da assinatura formal de T.op.
5. A precondição de S.op deverá ser igual ou mais fraca que a precondição
de T.op. Em particular, cada argumento formal de entrada para S.op de-
verá ser um supertipo (ou o mesmo tipo) do argumento formal de entrada
correspondente de T.op. (Esse é o princípio da contravariação.)
6. A pós-condição de S.op deverá ser igual ou mais forte que a pós-condição
de T.op. Em particular, cada argumento formal de saída de S.op deverá
ser um subtipo (ou o mesmo tipo) do argumento formal de saída corres-
pondente de T.op. (Esse é o princípio da covariação.)

11.3 O Princípio do Comportamento Fechado


Nas seções precedentes, examinamos o princípio da conformidade de tipo. Res-
peitar a conformidade de tipo é necessário para sermos capazes de desenhar
hierarquias de classes robustas; entretanto a conformidade de tipo é necessá-
rio não é o bastante. Informalmente falando, a conformidade de tipo por si
própria ocasiona desenhos saudáveis somente em situações de apenas leitura
(read-only), isto é, quando operações de acesso são executadas. (Talvez você se
recorde do belo pedaço de texto “quando qualquer operação de acesso do objeto
for executada”, na definição da conformidade de tipo da seção 11.2.)
Para lidar com situações em que são executadas operações modificadoras,
nós, de maneira idêntica, necessitamos do princípio do comportamento fecha-
do. Este princípio tem como requisito que o comportamento herdado por uma
subclasse a partir de uma superclasse, respeite a invariante da subclasse.
Sem esse princípio, talvez desenhemos subclasses com operações modificado-
ras que tenham comportamento propenso a erro.

Em uma hierarquia de herança baseada em uma hierarquia de


tipo/subtipo, a execução de qualquer operação em um objeto da classe
C — incluindo qualquer operação herdada da(s) superclasse(s) de C —
deverá obedecer à invariante de classe de C. Esse é o princípio do
comportamento fechado.

Como exemplo do princípio de comportamento fechado, vamos examinar


como o comportamento definido em uma superclasse Polígono poderá afetar os
objetos de uma subclasse Triângulo. É importante lembrar, à medida que avan-
çarmos neste exemplo, que, em cada um dos dois casos a seguir, o objeto re-
298 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

ferido é da classe Triângulo, mas o comportamento é definido por uma operação


da classe Polígono.
Caso 1: Para este caso, assumiremos que o objeto tri1 é o triân-
gulo esquerdo da Figura 11.8, enquanto a operação é a operação
mover da classe Polígono, que faz com que um objeto se mova,
digamos, um centímetro para a direita.

Figura 11.8 Polígono.mover, conforme aplicado ao triângulo


tri1, preserva sua triangularidade.

Após o movimento, tri1 ainda é um triângulo. Essa pequena representa-


ção do comportamento de Polígono deixa um objeto que estava no espaço-es-
tado de Triângulo ainda nesse mesmo espaço-estado. Dizemos que a subclasse
Triângulo está fechada segundo o comportamento definido pela superclasse
operação mover.
Caso 2: Novamente, assumiremos que o objeto tri1 é um triân-
gulo. Entretanto, desta vez consideraremos uma operação acres-
centarVértice (herdada da classe Polígono), que agrega um
vértice a um polígono. (Veja a Figura 11.9.) Após essa transição,
tri1 não será um triângulo; ele será um quadrilátero. A subclas-
se Triângulo não está fechada segundo o comportamento definido
pela operação acrescentarVértice da superclasse Polígono.

Figura 11.9 Polígono.acrescentarVértice, (erradamente) aplicado ao triângulo


tri1, destrói sua propriedade de triangularidade.
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 299

Conforme vimos no exemplo do caso 2, o fechamento de uma subclasse se-


gundo o comportamento de suas superclasses não acontece automaticamente.
Você tem de “desenhá-lo nela”. Portanto, na qualidade de desenhista de uma
subclasse, você deverá muitas vezes, deliberada e explicitamente, suprimir as
operações da superclasse as quais, de outra forma, violariam a invariante da
subclasse.
Neste exemplo, como desenhista de Triângulo, você deverá efetuar uma
das três ações corretivas a seguir:

• evitar a herança de acrescentarVértice;


• suprimir acrescentarVértice de forma que ela não tenha qualquer efei-
to (possivelmente também criando uma exceção); ou
• estar preparado (na operação acrescentarVértice) para reclassificar o
objeto Triângulo como Retângulo, se aquele comportamento, que não
preserva o fechamento de Triângulo, for admissível para a aplicação.9

O desenhista de uma classe, em geral, tem o dever de assegurar o fecha-


mento de comportamento da classe. Os projetistas das demais classes não de-
veriam se preocupar com a manutenção da invariante de classe.
Entretanto, nunca custa verificar. Se você estiver desenhando uma classe
que envia uma mensagem para um objeto invocando uma operação modifica-
dora, é preciso verificar quanto ao fechamento de comportamento no objeto
destinatário da classe. Se você enviar a mensagem assumindo o caso geral (su-
perclasse), deve ficar preparado para o caso de o objeto recusar a mensagem
ou simplesmente retorná-la sem qualquer ação. Se isso constituir um proble-
ma, então antes de você enviá-la, será preciso tomar uma das seguintes ati-
tudes:

• verificar a classe do objeto destinatário em run-time, ou


• restringir o polimorfismo na variável que aponta para o objeto desti-
natário, ou
• desenhar a mensagem supondo que o objeto destinatário pertença à
classe mais baixa, e mais específica, na hierarquia relevante — ou
seja, a classe com a maior restrição em seu comportamento.

9. O exercício 4 no final do capítulo acompanha esse último ponto e sugere uma modificação
mais drástica — porém mais robusta — para o desenho de herança.
300 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

No próximo capítulo, utilizaremos os conceitos e princípios deste capítulo


e do Capítulo 10 para examinarmos problemas específicos que surgem da he-
rança, do polimorfismo e da generalidade (aliás, classes parametrizadas).

11.4 Resumo
Este capítulo introduziu os princípios da conformidade de tipo e do comporta-
mento fechado, que nos ajudam a identificar problemas potenciais em nossos
desenhos de hierarquias de classes.
O princípio da conformidade de tipo diz que a classe B é um subtipo da
classe A somente se um objeto da classe B for aceitável em qualquer contexto
em que um objeto da classe A é esperado, e caso não for executada qualquer
operação modificadora. Uma hierarquia superclasse/subclasse robusta seguirá
este princípio: se a classe B é uma subclasse da classe A, então B se conforma
à A.
Para se atingir essa propriedade, você deverá assegurar o seguinte: a in-
variante de cada subclasse é, no mínimo, tão forte quanto a invariante de sua
superclasse; cada operação na superclasse tem uma correspondente operação
na subclasse com o mesmo nome e assinatura; cada precondição da operação
da subclasse não é mais forte do que a correspondente operação na superclas-
se; e cada pós-condição da operação da subclasse é pelo menos tão forte quanto
a operação correspondente na superclasse. Estes últimos dois princípios são
denominados, respectivamente, princípio da contravariação e da covariação;
eles são muito importantes quanto à determinação das classes corretas para
os argumentos de operações de subclasses.
Cada classe em uma hierarquia de classes sadia igualmente obedecerá ao
princípio do comportamento fechado. Ele requer que o comportamento que
uma subclasse herda, de uma ou de várias superclasses, respeite a invariante
da subclasse. Um desenhista de subclasse pode alcançar esse princípio ao efe-
tuar o seguinte: não permitindo heranças de comportamento conflitante; su-
primindo operações herdadas com comportamento conflitante; ou migrando
para outra classe um objeto que tenha violado sua invariante de classe.
Ainda que você possa ter achado que alguns dos tópicos neste capítulo se-
jam difíceis ou obscuros, você descobrirá que eles, em breve, se tornarão bas-
tante familiares se você se referir aos mesmos continuamente quando
construir hierarquias de classes e código de desenho orientado a objeto. Feliz-
mente, você descobrirá ainda que 90% das situações de desenho orientado a
objeto com as quais você se depara são claras, sem apresentar dificuldades, e
não requerem um conhecimento muito profundo de desenho. Mas, quando
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 301

você adentrar naqueles embaraçosos 10%, o conhecimento dos princípios deste


capítulo diferenciará você da população comum orientada a objeto.

11.5 Exercícios
1. O que aconteceria se, no exemplo da classe Funcionário com a subclasse
Gerente da seção 11.2.2, você não conseguisse desenhar a precondição de
calcularBônus no mínimo tão fraca e sua pós-condição no mínimo tão forte
na subclasse? (Por exemplo, as demandas de negócio dos usuários podem
dominar os clamores da sua consciência de desenho.) Uma vez que isso
pode significar que Gerente não constitui um subtipo verdadeiro de Fun-
cionário, você, em conseqüência, deve recompor sua estrutura de classe?
2. Dado que uma operação abstrata pode, por definição, jamais ser invoca-
da, faz algum sentido para uma operação desse tipo ter uma precondição
e uma pós-condição? (Dica: considere as precondições e pós-condições das
operações concretas nas subclasses que omitem a operação abstrata.)
3. Tome a classe Retângulo e considere uma de suas operações, girar, que
roda um retângulo em seu próprio plano segundo um certo ângulo:

girar (ânguloDeRotação)

Considere também Quadrado, uma subclasse de Retângulo, que herda a


operação girar. Após essa operação girar ser executada em um quadrado
ou em um retângulo, um quadrado continua a ser um quadrado e um re-
tângulo continua a ser um retângulo. Suas invariantes fundamentais se
aplicam, da mesma forma que anteriormente, à operação girar ao ser exe-
cutada. Sem problema!
Mas agora considere outra operação de Retângulo, escalaHorizontal, que
alarga um retângulo em uma dimensão:

escalaHorizontal (fatorDeEscala)

Um retângulo prolongado ainda é um retângulo. Mas um quadrado pro-


longado não é mais um quadrado! A invariante de classe w1 = h1 está
desfeita. O comportamento prescrito por escalaHorizontal mantém um re-
tângulo dentro do espaço-estado de sua classe, mas não mantém um qua-
drado dentro do seu espaço-estado. Retângulo é, portanto, fechado
segundo escalaHorizontal; contrariamente a Quadrado. Se não lhe é permi-
tido modificar a hierarquia de classes total, quais são algumas de suas
302 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

opções no desenho de Quadrado para preservar seu fechamento segundo


escalaHorizontal?
4. Conforme vimos na seção 11.3, acrescentar um vértice a um triângulo, re-
tângulo ou hexágono, viola o princípio do comportamento fechado. Caso
seja permitido a você modificar a hierarquia de classes total, que classe
você inseriria entre Polígono e Triângulo, Retângulo e Hexágono, para pre-
servar o comportamento fechado?

11.6 Respostas
1. Eis aqui uma solução. Se a discrepância em calcularBônus for a única saí-
da da classe Gerente por ser um subtipo verdadeiro, então você provavel-
mente não incomodaria a hierarquia de herança de classe. Mas você deve
documentar bem a discrepância — tanto na descrição completa da classe
como na operação transgressora específica. Verifique cuidadosamente
quanto a este tipo de problema ao desenhar certos ensaios; pois se ele es-
corregar entre os dedos, mais cedo ou mais tarde causará uma interrup-
ção polimórfica.
Mas, cuidado, porque essa solução é como brincar com fogo. Seria pru-
dente de sua parte recompor a hierarquia de classes, conforme mostrado
na Figura 11.10, e enfrentar esse problema de uma vez por todas, mesmo
se algumas aplicações existentes necessitarem de modificação para utili-
zar o novo nome de classe, NãoGerente.
Observe que agora os não gerentes têm a própria classe deles, ao lado
da classe dos gerentes. Visto que o particionamento entre as duas classes
é completo, nunca precisaremos criar um objeto da classe Funcionário, e
Funcionário pode, por conseguinte, razoavelmente ser desenhada como
abstrata. Presumivelmente, a operação calcularBônus também poderá ser
abstrata, porque, com base na descrição do plano direcionado a funcioná-
rios neste capítulo, não parece existir um algoritmo que calcule bônus de
gerentes e de não gerentes.
Graças à estrutura utilizada na Figura 11.10, não há qualquer proble-
ma de covariação ou contravariação entre NãoGerente.calcularBônus e Ge-
rente.calcularBônus, porque Gerente não é uma subclasse de NãoGerente.
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 303

Figura11.10 A hierarquia de classes com uma classe


NãoGerente decomposta de Funcionário.

2. Suponha que uma operação abstrata C.op, com precondição pré e pós-con-
dição pós, seja suprimida pelas operações concretas op1, op2,...,opn nas
respectivas subclasses de C. As seguintes assertivas são, portanto, verda-
deiras:

pré = pré1 and pré2 ... and prén


(onde pré1 é a precondição de op1)

pós = pós1 or pós2...or pósn


(onde pós1 é a pós-condição de op1)

A primeira expressão garante a contravariação, pelo fato de que qualquer


préi deverá ser mais fraco do que pré (desde que pré implica préi). A segunda
expressão garante a covariação, pelo fato de que qualquer pósi deverá ser mais
forte do que pós (desde que pósi implica pós).
Se você não puder invocar uma operação abstrata, essa discussão de pré
e pós-condições de uma operação abstrata talvez pareça acadêmica. Mas ela é
relevante quando você considerar um desenho típico orientado a objeto (simi-
lar ao que vimos na seção 11.2.2 para calcularBônus): um objeto remetente (di-
304 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

gamos sendobj) interage através de uma série de objetos de várias classes


(subclasses de um ancestral comum), enviando a mesma mensagem para cada
objeto, e invocando, por meio do polimorfismo, a versão de uma operação que
se encaixe na respectiva subclasse do objeto.
O objeto enviando a mensagem, sendobj, da mesma forma pode estar in-
vocando a operação comum — a operação abstrata na superclasse —, uma vez
que ele não tem qualquer idéia de qual objeto pertence a determinada subclas-
se. Em outras palavras, para sendobj cumprir seu “contrato” com os objetos
destinatários, sendobj deverá estar utilizando a precondição, pré, e a pós-con-
dição, pós, para assegurar a exatidão das mensagens.
Entretanto, tenho visto situações em que sendobj não pode garantir, por exem-
plo, a satisfação de pré quando ele envia uma mensagem. (Para um exemplo
extremo, imagine que as precondições pré1, pré2, ... , prén sejam incompatíveis
umas com as outras, de forma que a combinação booleana pré1 e pré2 ... and
prén seja atribuída como falsa. Por conseguinte, sendobj nunca poderá satisfa-
zer pré!) Se sendobj não pode garantir pré, então você deve se afastar do poli-
morfismo e fazer o teste de sendobj para a classe (digamos Ci) à medida que
prosseguir. Então sendobj deverá certificar-se de que ele satisfaz cada uma
das precondições específicas préi antes de enviar uma mensagem àquele objeto
em particular.
3. O problema é que não podemos simplesmente deixar Quadrado como se
herdasse escalaHorizontal. Dessa forma, sem modificar a hierarquia de
classes, dispomos de quatro opções no desenho de Quadrado.
a. Provocar uma exceção quando um quadrado violar sua invariante de
classe.
b. Suprimir escalaHorizontal de forma que ela não faça nada para os qua-
drados.
c. Suprimir escalaHorizontal com algum comportamento diferente que
preserve a invariante de classe de Quadrado. (Talvez invocar escala-
Vertical com um fatorDeEscala similar de forma que um quadrado se
prolongue igualmente em ambas as dimensões.)
d. Apanhar a abordagem mais drástica de deixar um quadrado ficar es-
ticado. A partir daí, mudar sua classe — em outras palavras, “fazer
migrar” — de Quadrado para Retângulo.
Todas essas quatro opções de desenho preservam a invariante de classe
da classe do objeto. Confessamente, entretanto, a última opção atua um
tanto maliciosamente ao mudar a classe do objeto para adequar seu novo
estado. A opção por você selecionada envolve o exame de como Quadrado
Cap. 11 CONFORMIDADE DE TIPO E COMPORTAMENTO FECHADO 305

será utilizado em sua aplicação atual, bem como de que forma ele poderá
ser reutilizado em aplicações futuras.
4. Polígono deveria ter duas subclasses, PolígonoDeLadoFixo e PolígonoDeLa-
doVariável. Triângulo (e classes similares) deveria ser uma subclasse de
PolígonoDeLadoFixo. Essa abordagem permite que um desenhista posicio-
ne a operação acrescentarVértice no PolígonoDeLadoVariável e remova a ne-
cessidade de se suprimir acrescentarVértice em classes representativas de
polígonos de lados fixos. (Se PolígonoDeLadoVariável tem subclasses, então
você deveria nomeá-las TriânguloMutável, RetânguloMutável e assim por
diante, caso pudessem ser acrescentados ou removidos vértices.)
Observe que essa solução, com suas subclasses decompostas, se parece
com a da Figura 11.10.
O s perigos da herança e do
polimorfismo
FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML
OS PERIGOS DA HERANÇA E DO POLIMORFISMO
12.Os Perigos da Herança e do Polimorfismo

A herança e o polimorfismo situam a orientação a objeto à parte das formas


convencionais de se construir software. Entretanto, muito embora a
construção de herança seja muito poderosa, ela, de maneira idêntica, é talvez
a construção de software utilizada de maneira mais excessiva desde o surgi-
mento da assertiva goto. Os iniciantes na técnica orientada a objeto sentem
que têm de desenhar fazendo uso da herança em toda e qualquer oportunida-
de possível, a fim de mostrar que eles “adentraram” no mundo da orientação
a objeto. O resultado: problemas sendo deturpados para enquadrar-se em um
“desenho baseado em herança” até o ponto de certos desenho tornarem abso-
lutamente não implementáveis.
A primeira seção deste capítulo trata de quatro formas pelas quais as
pessoas forçam a herança em seus desenhos orientados a objeto, juntamente
com minhas sugestões de alternativas a respeito de uma utilização adequada
da herança nessas situações de desenho. A segunda seção deste capítulo ex-
plora o perigo do polimorfismo.

12.1 Abusos da Herança


Eu selecionei os quatro exemplos presentes nesta seção a partir de projetos
efetivos em reais locais de trabalho, em que pessoas verdadeiras estavam apli-
cando orientação a objeto; em muitos casos, pela primeira vez. Alguns dos de-
senhos intratáveis que cito estavam secreta e horrivelmente codificados;
outros provocaram disputas que se tornaram enfadonhas e “violentas” e — em
um caso específico — foram destruidoras de equipes de trabalho.
Os objetivos desta seção são examinar modelos diferentes de herança e
evitar perda de ânimo e tempo quanto ao seu próximo projeto, ressaltando

306
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 307

onde a herança é e não apropriada. Neste capítulo, os exemplos passam da


utilização inadequada e abusiva aos usos mais sutis e questionáveis da heran-
ça. A propósito, alterei os nomes das classes nos exemplos para proteger os
perpetradores responsáveis por essas ações.

12.1.1 Conjuntos equivocados


O diagrama de herança de classe, na parte superior da Figura 12.1, mostra a
classe Aeronave com suas quatro supostas subclasses: Asa, Cauda, Motor e Fu-
selagem. Esse “desenho” jamais poderá ser expresso na forma de código, por
não haver qualquer forma compreensível de programação das subclasses
quanto a satisfazer as necessidades da aplicação. O desenhista, obviamente,
confundiu os conceitos de herança de classes e de composição de objetos. Se
lermos o diagrama da forma como ele está apresentado, presumiremos que
uma cauda “é uma espécie de aeronave”, e que, de maneira idêntica, uma asa
“é uma espécie de aeronave”.

Figura 12.1 Dois “desenhos” para a classe Aeronave — o


que está errado com essa figura?
308 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

O diagrama, na parte inferior da Figura 12.1, mostra a herança múltipla


como outra suposta solução de desenho referente à composição de objetos. O
desenhista desta outra classe Aeronave interpreta o diagrama dele como:
“Uma aeronave é uma asa, uma cauda, um motor e uma fuselagem”. Isso pa-
rece praticamente correto. Entretanto, o caminho verdadeiro para interpretar
o diagrama é: “Uma aeronave é, simultaneamente, uma espécie de asa, uma
espécie de cauda, uma espécie de motor e uma espécie de fuselagem”. E isso,
certamente, está bem errado!
O desenhista estava programando sublimemente a partir desse inefável
“desenho” de Aeronave quando o encontrei. Ele somente trouxe o desenho para
meus cuidados quando começou a ter problemas ao programar o fato de que
uma aeronave tem duas asas.
No entanto, seus problemas eram ainda mais profundos do que esse par-
ticular desenho: o desenhista era um novato na orientação a objeto; não tinha
lido sequer um único livro; não tinha recebido qualquer treinamento; tinha so-
frido lavagem cerebral de um colega no sentido de pensar que a herança múl-
tipla era a maior de todas as coisas desde a personalidade múltipla; e tinha
trabalhado em um local em que as críticas de seus colegas de profissão eram
desencorajadas porque elas interfeririam com o trabalho. Desconsiderando
isso, como eles diziam, tudo o mais estava ótimo!1

12.1.2 Hierarquia invertida


O exemplo de herança na Figura 12.2 mal assegura uma segunda olhadela rá-
pida porque sua estrutura corresponde exatamente a um diagrama normal de
organização. Um funcionário reporta a um gerente e um gerente reporta a um
membro de diretoria. Mas há um problema. O que o diagrama revela na rea-
lidade é: “Um funcionário é uma espécie de gerente, e um gerente é uma es-
pécie de membro de diretoria”.
Eu suponho que a realidade se encontra no caminho inverso, ou seja,
“Um membro de diretoria é uma espécie de gerente, e um gerente é uma es-
pécie de funcionário”. (Por simplicidade, vamos assumir que os membros de
diretoria são funcionários.) Para retratar isso, simplesmente inverteríamos a
Figura 12.2, e colocaríamos Funcionário, que constitui a classe mais geral, no
topo. (Conforme vimos em exercício do capítulo anterior, esse é somente o iní-
cio. A seguir, poderemos querer decompor essa classe em várias subclasses.)

1. Esse exemplo é muito similar ao exemplo do Planador, de composição de objetos, constante


na seção 4.3.1., que talvez você queira rever.
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 309

Figura 12.2 Parece correto — mas está?

12.1.3 Confundir classe com instância


O exemplo de herança múltipla na Figura 12.3 é tremendamente sutil. Quan-
do me deparei com o mesmo em um tipo de ensaio, tive o pressentimento de
que ele estava errado, mas tive uma dificuldade enorme em explicar o por quê.
Minha dificuldade era ainda maior porque a aplicação efetiva constituía uma
aplicação obscura e técnica, e meus colegas, autores desse ensaio, acreditavam
que eu estava desqualificado para emitir qualquer tipo de julgamento a esse
respeito.

Figura 12.3 O que Panda significa, na realidade?

O caminho para entender esse problema é a questão: quais são as instân-


cias das três classes na Figura 12.3? As instâncias de Panda são, por exemplo,
An-An, Chu-Chu, Ling-Ling, Miou-Miou, Hee-Hee e Oh-Oh. As instâncias de
Urso são, por exemplo, Yogi, Teddy, Winnie, Paddington e Fred. Entretanto,
as instâncias de EspécieAmeaçadaDeExtinção constituam espécies completas,
310 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

tais como, por exemplo, o rato-almiscarado de nariz arrebitado, o rato que se


agarra a árvores, menos sarapintado, o rato relativamente grande da Suma-
tra, o rato identicamente grande de Bornéu, o codhurr americano, a pulga
cantante da Letônia e o nodenik de papada em forma de rede.
Falando de outra forma, duas das classes (Panda e Urso) têm animais in-
dividuais como instâncias, ao passo que a terceira classe (EspécieAmeaçada-
DeExtinção) tem espécies completas como instâncias. Seria plenamente correto
dizer que “Ling-Ling é uma instância de panda”, ou “Ling-Ling, por ser um
panda, também é uma instância de urso”. Entretanto, não é correto dizer que
“Ling-Ling é uma instância de espécie ameaçada de extinção”. Por conseguin-
te, a classe Panda poderia ser herdeira de Urso mas não de EspécieAmeaçada-
DeExtinção.2
Dessa forma, como poderíamos desenhar o fato de que algumas espécies
estão ameaçadas de extinção? A Figura 12.4 ilustra a resposta:

Figura 12.4 As hierarquias de herança corrigidas.

O diagrama à esquerda mostra a classe Panda, herdeira de Urso: um pan-


da é uma espécie de urso, mas como {incompleto} nos confirma, esse não é o
único tipo de urso. O diagrama à direita mostra EspécieAmeaçadaDeExtinção e
EspécieNãoAmeaçadaDeExtinção, classes herdeiras de Espécie: as duas classes
representam mutuamente subconjuntos exclusivos do conjunto completo das
espécies.
As duas classes à esquerda tratam de animais, enquanto a hierarquia de
classe à direita lida unicamente com espécies. Mas como podemos vincular es-
sas duas facetas? De que forma desenhamos esse aspecto, ou seja, que os pan-
das, e algumas outras espécies, correm risco de extinção?

2. Tudo bem, sei que um panda é uma espécie de guaxinim, mas, por favor, queira ser compla-
cente comigo.
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 311

Um desenho agregaria um atributo de instância, estáEmRiscoDeExtinção:


Booleano, em Urso (ou em sua superclasse, Animal). Embora essa saída pudes-
se funcionar, seria um tanto excessiva porque ela confere generalidade de-
mais. Por exemplo, seríamos capazes de registrar que Yogi estaria sob risco
de extinção, enquanto Paddington não. Eu não acredito que esse seja o propó-
sito dessa aplicação.
Em vez disso, conforme mostrado pela Figura 12.5, deveríamos dar à
classe Animal o atributo espécie (e a variável interna espécie) para vinculá-la
a um objeto Espécie. Cada subclasse de Animal representa uma espécie (tal
como Urso, Panda, Sapo e assim por diante), e espécie detém o valor fixo apro-
priado para os objetos daquela subclasse. Por exemplo, a cada objeto Urso po-
deria ser atribuído um valor quando o mesmo é gerado, ou cada objeto Urso
poderia recuperar o valor de seu atributo espécie a partir de uma constante
de classe nossaEspécie: Espécie = urso na classe como um todo.

Figura 12.5 As hierarquias de herança corrigidas, com mais detalhes.


312 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Seja qual for o meio como Urso.espécie obtém seu valor, os objetos da clas-
se Urso poderiam procurar pelas suas espécies — e as propriedades associadas
de suas espécies — em run-time, com o código tal como:

self.espécie.estáEmRiscoDeExtinção;
self.espécie.pesoMáximo;

pesoMáximo: Peso, por exemplo, seria um atributo de instância de Espécie. Es-


táEmRiscoDeExtinção: Booleano seria um atributo de instância (efetivamente,
uma constante) em ambas as classes, EspécieAmeaçadaDeExtinção e NãoAmea-
çadaDeExtinção, imputada, respectivamente, como true e false.3
As pessoas que vêem esse exemplo do panda freqüentemente dizem para
mim: “Meu Deus, esse foi um exemplo notavelmente pungente da utilização
inadequada do conceito de herança. Esses comentários provocam lágrimas nos
meus olhos. Mas ele é absolutamente inútil para meu local de trabalho, que
lida com aplicações em processos de manufatura, e não com guaxinins decres-
cendo em população”. Uma vez que eu não poderia resistir a um apelo vee-
mente como esse, veja aqui a Figura 12.6.
Embora as duas aplicações façam parte dos mundos distintos da vida ani-
mal e do processo de manufatura, há uma forte semelhança estrutural entre
as Figuras 12.5 e 12.6: a primeira é baseada em torno da agregação de Animal
em Espécie, e a última em torno da agregação de ItemDeProduto em LinhaDe-
Produto. A classe Aparelho, análoga a Urso na Figura 12.5, é uma subclasse de
ItemDeProduto. (Embora Aparelho seja a única subclasse por mim mostrada,
outras subclasses tais como Dispositivo, Engenhoca e Invento poderiam existir.)
No lado direito da Figura 12.6, as classes LinhaDeProdutoManufaturado e
LinhaDeProdutoComprado são herdeiras de LinhaDeProduto. Essas duas classes,
que são mutuamente exclusivas, formam um particionamento completo de Li-
nhaDeProduto.4 A associação na parte inferior direita registra os distribuido-
res de uma linha de produto. (Algumas linhas de produtos comprados ainda
não tinham atribuído a elas um distribuidor — por isso o 0 na multiplicidade
mais baixa.)

3. estáEmRiscoDeExtinção poderia, diferentemente, ser desenhada como um atributo de instân-


cia de Espécie, em lugar de EspécieAmeaçadaDeExtinção e EspécieNãoAmeaçadaDeExtin-
ção. Veja o exercício 1 no final deste capítulo.
4. O exercício 2 deste capítulo recorre à possibilidade de uma sobreposição entre LinhaDePro-
dutoManufaturado e LinhaDeProdutoComprado.
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 313

Figura 12.6 Uma estrutura análoga de sistema de negócio.

12.1.4 Utilização inadequada


Em nosso próximo exemplo, o qual é uma versão de outro desenho de uma
aplicação efetiva, precisamos rememorar o comprimento, a largura e a altura
de quartos — provavelmente quartos de hotel — que em nossa particular apli-
cação são tratados como cubóides. Da mesma forma, precisamos saber o volu-
me de cada quarto. Agora que já dispomos da classe Cubóide em nossa
biblioteca de classes, desenhamos uma classe Quarto que é simplesmente her-
deira de Cubóide; conforme visto na Figura 12.7.
314 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Figura 12.7 A classe Quarto é desenhada


simplesmente para ser herdeira de Cubóide.

Até o momento, essa representação parece ótima. Um objeto da classe


Quarto poderá retornar seu volume simplesmente executando a operação obter
volume (implementada como uma função) que Quarto herda de Cubóide; e, não
é necessário qualquer código. Além do mais, uma vez que um quarto é um cu-
bóide, o item “é um requisito de herança válido” é satisfeito. Entretanto, é a
partir daí que o desenho começa a desintegrar-se.
O primeiro problema é o comportamento que Quarto herda de Cubóide.
Esse comportamento é proveniente das operações de Cubóide, tais como am-
pliar, rodar e assim por diante. Como esse comportamento é ilegal para Quarto
(ao menos, fora do reino da fantasia), deveríamos suprimi-lo — isto é, cance-
lá-lo — em Quarto. Por outro lado, poderíamos permitir que as operações per-
manecessem como parte do comportamento oficial de Quarto. Dessa forma,
teríamos de confiar nas pessoas para que utilizassem essas operações judicio-
samente, porque, na realidade, rodar um quarto no espaço tridimensional
apresentará efeitos bem curiosos!
Todavia, problemas maiores surgem quando temos de lidar com quartos
de outros formatos. Digamos que alguns quartos fossem cilíndricos. A heran-
ça, que deu tão certo para os cubóides, também deveria funcionar para os ci-
lindros. Assim, portanto, como mostrado pelo diagrama da esquerda da Figura
12.8, a classe Quarto herda vários elementos de Cubóide e Cilindro.
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 315

Figura 12.8 Duas tentativas para introduzir quartos cilíndricos.

Mas o que isso significa? A herança múltipla implica que determinado


quarto é tanto um cilindro quanto um cubóide. Isso produziria um quarto de
formato muito estranho, e ele certamente não corresponderia ao quarto pre-
tendido pelo desenhista. Quando perguntei ao desenhista sobre essa herança
múltipla, ele me disse que o diagrama indicava que um quarto poderia ser um
cilindro ou um cubóide. Outro desenhista disse: “Agora, você dispõe do diagra-
ma de ponta-cabeça”, e prosseguiu expondo as virtudes do diagrama no lado
direito da Figura 12.8.
Nesse momento, estamos caminhando muito rapidamente para chegar a
lugar algum, embora a hierarquia na qual Cubóide e Cilindro são herdeiras de
Quarto vá, pelo menos, trabalhar — até certo ponto. Se gerarmos um objeto,
quer de Cubóide, quer de Cilindro, conseqüentemente esse objeto herdará todas
as propriedades essenciais de Quarto. Entretanto, ainda temos o problema do
comportamento estranho (tal como ampliar e rodar), que agora não temos mais
chance de suprimir.
O segundo desenho na Figura 12.8 apresenta até mesmo problemas mais
profundos. Sabemos do Capítulo 9 que não poderíamos sobrecarregar uma
classe de um domínio baixo (Cubóide ou Cilindro) com uma de um domínio
mais alto (Quarto). Caso tivéssemos procedido dessa forma, nossa biblioteca de
geometria necessitaria da classe Quarto para funcionar. Igualmente, uma vez
que nem todos os cubóides são quartos, a classe Cubóide teria coesão de ins-
tância mista, bem como coesão de domínio misto.
A raiz do problema está em minha afirmação original: “Um quarto é um
cubóide”, a qual “lancei no ar para tirar a sorte” — para justificar herança.
Isso foi um “truque de prestidigitação”. Uma afirmação mais precisa é: “Um
316 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

quarto tem o atributo do formato. O formato de todos os quartos nesta aplica-


ção é o de um cubóide”, o que faz uma diferença importante. Um novo desenho
para Quarto aparece na Figura 12.9.

Figura 12.9 Quarto tem um formato, da classe


FormatoFechado3D, e pede seu volume de formato.

Dessa vez, Quarto tem uma variável de instância formato, que aponta
para um objeto da classe FormatoFechado3D (ou de uma classe de suas sub-
classes, tais como Cubóide, Cilindro ou Tetraedro). Em outras palavras, a classe
Quarto contém a declaração:

var formato: FormatoFechado3D;

Quando da geração de determinado quarto, a variável formato é atribuída


para um objeto do correto formato e tamanho para esse dado quarto. A ope-
ração Quarto.volume agora funciona pedindo ao objeto referenciado pela variá-
vel formato para calcular volume, conforme mostrado na Figura 12.9. A
fórmula particular realmente avaliada (seja a fórmula do volume do cubóide,
seja a do volume do cilindro) dependerá do formato verdadeiro do quarto —
polimorfismo, novamente.
Essa técnica de acessar o código em outra classe é chamada de transmis-
são de mensagens (message forwarding). Um objeto da classe Quarto expede o
volume de mensagens para outro objeto da classe Cubóide (ou o que for). A
abordagem de desenho da transmissão de mensagens, que é uma alternativa
para herança, não lhe confere automaticamente acesso a todos os recursos de
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 317

outra classe. Como substituto, você tem de visar esse acesso, mensagem por
mensagem, para os atributos e operações da outra classe que você procura.
Talvez você queira rememorar as dores de cabeça que os desenhos originais
baseados em herança provocaram, e conferir como esse desenho baseado em
transmissão de mensagens as conseguiu debelar.5

12.2 O Perigo do Polimorfismo


O polimorfismo promove concisão na programação orientada a objeto, permi-
tindo que uma operação seja definida com o mesmo nome em mais de uma
classe, e que uma variável se refira a um objeto de mais de uma classe. O po-
limorfismo, portanto, capacita ao meio operante a selecionar automaticamente
a operação correta a executar, como resultado de uma mensagem, e sem a ne-
cessidade de uma assertiva de caso complicada.
Assim, tanto as operações como as variáveis podem exibir polimorfismo.
Em um bom desenho, essas duas facetas do polimorfismo trabalham em har-
monia. Em um desenho imperfeito, entretanto, o polimorfismo traz algum pe-
rigo: um objeto pode receber uma mensagem não compreendida pelo mesmo
e, conseqüentemente, pode provocar uma exceção fatal em run-time.
As seções 12.2.1 e 12.2.2 introduzem, respectivamente, o polimorfismo de
operações e de variáveis. A seção 12.2.3 explora os problemas do desenho po-
limórfico em termos de mensagens gerais a objetos. A seção 12.2.4 explora um
caso especial: o perigo do polimorfismo no desenho de classes parametrizadas.

12.2.1 Polimorfismo de operações


Para explicar o risco de um objeto não compreender a mensagem enviada a
ele — e como evitar esse tipo de risco —, preciso introduzir alguns termos no-
vos.

O escopo de polimorfismo de uma operação op é o conjunto de classes


sobre as quais é definida op. Um escopo de polimorfismo (SOP —
Scope of Polymorphism) que forma um ramo da hierarquia de heran-
ça — ou seja, uma classe A juntamente com todas as suas subclasses
— é denominado cone de polimorfismo, tendo A como o vértice de poli-
morfismo.

5. Alguns autores utilizam o termo delegação (delegation) para transmissão de mensagens. En-
tretanto, evitei esse termo, porque ele é utilizado geralmente mais para um conceito orientado
a objeto que está além do escopo deste livro. (Contudo, também defino “delegation” no Glos-
sário.)
318 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

A Figura 12.10 retrata uma árvore de hierarquia de classe. Se uma ope-


ração op é definida sobre cada uma das classes sombreadas, então essas clas-
ses sombreadas formam o cone de polimorfismo (COP — Cone of
Polymorphism). Ele é um cone em que as classes sombreadas formam um
ramo completo; a classe A é o vértice de polimorfismo (AOP — Apex of Poly-
morphism).6

Figura 12.10 A estrutura de um COP.

Como um exemplo mais concreto, se a operação obter área (ou obterÁrea,


se você preferir) for definida em Polígono, e em todas as subclasses de Polígono
— localmente ou via herança —, então o SOP de área formará um cone, com
Polígono no vértice. Veja a Figura 12.11.

6. Um dos meus alunos refere-se a A como “cabeça de cone do polimorfismo”. Não me deixe apa-
nhá-lo utilizando esse termo!
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 319

Figura 12.11 O COP para área.

A Figura 12.12 retrata outra árvore de hierarquia de classe, na qual a


operação op é definida sobre todas as classes sombreadas. Essa figura mostra
um SOP desigual (ragged), visto que as classes sombreadas não formam um
cone completo, composto por um ramo integral da árvore.

Figura 12.12 A estrutura de um SOP (desigual).


320 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Um exemplo concreto e simples de um SOP desigual para uma opera-


ção é um pouco mais complicado de se encontrar. Entretanto, a operação fa-
miliar imprimir (que envia informação sobre o objeto destinatário para um
objeto DriverDeImpressora) freqüentemente tem um conjunto “todo remenda-
do” de classes em seu SOP. Se imprimir é definido em PlanilhaEletrônica, Do-
cumentoDeTexto e MensagemDeE-Mail, então seu SOP não é um cone, porque
classes como Elefante e até mesmo a classe de “topo”, Objeto, não têm qual-
quer operação imprimir nelas definidas.7 Veja a Figura 12.13.

Figura 12.13 O SOP (desigual) para a operação imprimir (assumindo que


Elefante e Objeto não tem qualquer operação imprimir).

12.2.2 Polimorfismo de variáveis


Conforme mencionei anteriormente, o termo polimorfismo também se aplica a
variáveis, que, a qualquer momento, podem apontar para objetos pertencentes
a diferentes classes. Assim, agora defino o escopo de polimorfismo novamente,

7. Eu aqui assumo que PlanilhaEletrônica, DocumentoDeTexto e MensagemDeE-mail não têm


uma superclasse comum, tal como DocumentoImprimível. Também assumo, para este exem-
plo, que imprimir é uma operação aceitável para essas classes e não origina quaisquer pro-
blemas de coesão.
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 321

desta vez aplicando os termos a uma variável que detém o identificador de um


objeto em vez de uma operação.

O escopo de polimorfismo de uma variável v é o conjunto das classes


às quais os objetos referidos por v (durante a existência inteira de v)
podem pertencer.

O escopo de polimorfismo para uma variável é similar ao escopo de poli-


morfismo para uma operação: ambos os escopos compreendem um grupo de
classes. As classes no SOP para uma variável, entretanto, são as de todos os
objetos aos quais a variável pode referir-se a qualquer hora durante a execu-
ção do sistema. Podemos, de maneira idêntica, utilizar os termos cone de po-
limorfismo e vértice de polimorfismo para uma variável, da mesma forma que
esses termos foram utilizados para uma operação.
Veja aqui três exemplos para ilustrar o escopo de polimorfismo de uma
variável:

1. Digamos que a declaração var t: Triângulo permita que a variável T aponte


para qualquer objeto da classe Triângulo ou para descendentes de Triân-
gulo. (Essa é uma situação natural em Java, Eiffel ou C++, linguagens
nas quais o polimorfismo de uma variável é normalmente restrito aos
descendentes de uma dada classe.) Neste exemplo, portanto, o SOP da
variável forma um cone, tendo a classe Triângulo como vértice.
2. Digamos que se permita a uma variável v, a qualquer hora, apontar para
um objeto da classe Cavalo, Círculo ou Cliente. (Isso pode facilmente ocor-
rer em Smalltalk, linguagem na qual o polimorfismo de variáveis é tipi-
camente irrestrito; em outras linguagens, talvez você tenha de declarar v
como sendo da classe mais geral, Objeto.) Neste exemplo, então, o SOP da
variável não é um cone, visto que as classes Cavalo, Círculo e Cliente não
têm uma superclasse comum imediata para formar um AOP. Ao menos,
presumimos que elas não têm uma superclasse comum. Embora você pu-
desse introduzir uma classe absurda como uma superclasse de Cavalo, Cír-
culo e Cliente, para prover um AOP artificial para uma variável, sei que
você não seria capaz de fazer isso.8
3. Neste terceiro exemplo, digamos que (novamente, como em Smalltalk) te-
mos uma declaração var x: Objeto, na qual a classe Objeto está no topo da

8. Como veremos no decorrer do livro, algumas linguagens de programação orientadas a objeto


(tais como Eiffel) vão impor um COP em uma variável se você instruir o compilador.
322 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

hierarquia de classes. Em outras palavras, a variável x talvez aponte


para qualquer que seja o objeto (pelo fato de todas as classes serem des-
cendentes de Objeto). Desta vez, o SOP da variável realmente forma um
cone. De fato, esse cone é o maior de todos eles, porque seu vértice é a
classe “de topo” na hierarquia de herança.
Agora, vamos considerar o risco de enviar uma mensagem a um objeto
que não a entenda, e discutir como evitar esse problema.

12.2.3 Polimorfismo em mensagens


Uma mensagem é composta de uma variável que aponta para o objeto desti-
natário e de um nome de operação que especifica a operação a ser invocada.
Conforme vimos, tanto a variável como a operação têm um SOP. O relaciona-
mento entre esses dois SOPs tem um impacto significativo na confiabilidade
do sistema — ou na ausência da mesma. Conforme discuto a seguir, vou supor
que os SOPs da operação e da variável sejam COPs, — isto é, que eles formam
cones completos e perfeitos.
Denominarei nossa mensagem amostra de objdestinatário.operdestinatá-
ria, em que objdestinatário é a variável apontando para o objeto destinatário e
operdestinatária é a operação a ser invocada no objeto destinatário. Há dois
possíveis relacionamentos entre o COP de objdestinatário e o COP de operdes-
tinatária:
Caso 1: O COP de objdestinatário reside dentro do COP de oper-
destinatária. Em outras palavras, o COP da variável reside den-
tro do COP da operação. Veja a Figura 12.14.

Figura 12.14 O COP da variável objdestinatário reside dentro


do COP da operação operdestinatária.
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 323

Caso 2: Parte ou o todo do COP de objdestinatário cai den-


tro do COP de operdestinatária. Em outras palavras, uma parte
do COP da variável fica situada fora do COP da operação. Veja
a Figura 12.15.

Figura 12.15 Parte do COP da variável objdestinatário fica


situada fora do COP da operação operdestinatária.

No primeiro caso, tudo está bem com o desenho. Não importa para qual
objeto objdestinatário está apontando, esse objeto será de uma classe que “en-
tende” a mensagem operdestinatária. O segundo caso, entretanto, representa
um desenho miserável e não robusto. Esse desenhista está brincando com o
diabo, visto que é bem possível que em run-time, objdestinatário aponte para
um objeto sobre o qual a operdestinatária da classe não está definida. Se isso
ocorrer, então o programa provavelmente irá “sofrer interrupção” com um erro
de run-time.
Para um exemplo específico dos possíveis relacionamentos entre o SOP de
uma variável e o SOP de uma operação, considere a mensagem dispositivoDe-
Fábrica.ligar. Conforme mencionado a seguir, há dois casos:
Caso 1: A mensagem dispositivoDeFábrica aponta sempre para
um objeto da classe Torneira, Motor ou Luz, todos os quais podem
ser ligados (ou aberta — no caso da torneira). Assim, o SOP de
dispositivoDeFábrica está dentro do SOP de ligar (abrir), e tudo
estará bem. Entretanto, eu sugeriria alterar o nome da variável
para dispositivoOperável ou dispositivoAlterável para indicar que
ele se refere a um dispositivo que pode ser operado ou ligado
(aberto). Utilizo um complemento como alterável em meus no-
324 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

mes de variáveis na forma de uma palavra-código significando


“qualquer coisa que possa executar ligar (abrir)”.
Caso 2: A mensagem dispositivoDeFábrica refere-se a qualquer
elemento de hardware na fábrica, inclusive aos objetos da classe
Torneira, Motor, Luz, Tubo, Tanque, Porta, Alavanca e assim por
diante. Nem todas essas espécies de dispositivos “sabem como
ligar (abrir)”. Desta vez, portanto, uma grande parte do SOP de
dispositivoDeFábrica fica situada fora do SOP de ligar (abrir), e
há uma possibilidade significativa de um problema de run-time,
quando, por exemplo, é indicado a uma porta antiga e simples
que ligue (abra).
Sempre que você se entregar à suntuosidade da técnica que utiliza men-
sagens polimórficas, certifique-se de que você está estimando a magnitude do
SOP da operação e do SOP da variável destinatária da mensagem. Você pre-
cisa ser especialmente diligente se a variável destinatária ou a operação apre-
sentarem um SOP imperfeito.

12.2.4 Polimorfismo e generalidade


Uma classe parametrizada (denominada de classe modelo [template class] em
C++) é uma classe que toma o nome de classe como argumento sempre que um
de seus objetos é gerado, conforme discuti na seção 1.9. Desenhistas freqüen-
temente utilizam classes parametrizadas para construir recipientes tais como
listas, pilhas e árvores de classes.
Mas, semelhantemente às mensagens polimórficas que vimos na seção
12.2.3, as classes parametrizadas podem criar problemas em run-time por cau-
sa dos conflitos do escopo de polimorfismo. Para ilustrar isso, vou apresentar
uma classe parametrizada ÁrvoreDeClasse <ClasseDeNó>, (um tanto parecida
com o exemplo da árvore binária que vimos na seção 1.9).
A seguinte declaração gera uma árvore de classe específica:

árvoreDeNúmeroReal := ÁrvoreDeClasse <NúmeroReal>.Novo;

que cria um novo objeto, uma árvore de classe referida por árvoreDeNú-
meroReal, que deterá números reais em seus nós. Também poderíamos escre-
ver:

árvoreDeCliente := ÁrvoreDeClasse<Cliente>.Novo;
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 325

que deteria objetos da classe Cliente em seus nós. Dentro da classe Árvo-
reDeClasse, escreveríamos declarações tais como:

nó := ClasseDeNó.Novo;

A declaração anterior cria um novo nó da classe NúmeroReal (para o pri-


meiro objeto de ÁrvoreDeClasse anterior) ou da classe Cliente (para o segundo
objeto da ÁrvoreDeClasse anterior). De maneira idêntica, no código de Árvore-
DeClasse (ou seja, na operação imprimirÁrvore), poderíamos ver:

nó.imprimir

na qual se passaria uma mensagem ao objeto apontado pelo nó para “impri-


mir a si mesmo”. Em qualquer lugar do código em ÁrvoreDeClasses, poderíamos
ver a seguinte comparação:9

if novoItem.menorQue (nóAtual)
// cada um destes, novoItem e nóAtual, aponta para um objeto da
ClasseDeNó
then...

O problema com essa comparação é este: o desenhista de ÁrvoreDeClasse não


tem a menor idéia de qual a efetiva classe que será passada como um argumento
em run-time. Por exemplo, alguém poderia escrever ÁrvoreDeClasse<Fusela-
gem>.Nova, ÁrvoreDeClasse <NúmeroComplexo>.Novo ou ÁrvoreDeClasse<Ani-
mal>.Novo.
A primeira dessas três classes talvez não reconhecesse imprimir, e a segunda
talvez não reconhecesse menorQue, enquanto a terceira talvez não reconhecesse
nem imprimir nem menorQue. Portanto, existe um risco muito grande de que
aconteça um erro em run-time quando um objeto situado na árvore, por exemplo,
da classe Animal, receber a comunicação de “imprimir a si próprio”.
O problema ocorre porque o escopo de ClasseDeNó é ilimitado: em run-
time, qualquer classe poderia ser provida de ÁrvoreDeClasse para desempenhar
o papel de ClasseDeNó. Assim, o escopo de polimorfismo de nó: ClasseDeNó é
enorme, tão grande quanto poderia ser. Por outro lado, o escopo de polimor-
fismo das operações dentro de ÁrvoreDeClasse é, na verdade, muito pequeno:
corresponde à interseção dos SOPs das operações individuais (tais como impri-
mir, menorQue e assim por diante).

9. Em algumas linguagens, a sintaxe para essa comparação seria simplesmente if novoItem


<nóAtual then...
326 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

Portanto, o desenho de ÁrvoreDeClasse viola o princípio que delineei na se-


ção 12.2.3, que assegura que uma operação com um menor SOP não se aplica
a uma variável com um maior SOP. Algumas das variáveis em ÁrvoreDeClasse
têm SOPs que potencialmente residem muito fora dos SOPs das operações uti-
lizadas em ÁrvoreDeClasse.
Existem duas soluções para esse problema de desenho: a primeira é para
que todo e qualquer usuário de uma classe parametrizada seja responsável o
bastante para assegurar que a classe real fornecida em run-time (digamos,
ClasseProvida) fique dentro da interseção de SOP já descrita. Em outras pala-
vras, os objetos pertencentes à classe ClasseProvida devem ser capazes de re-
conhecer qualquer mensagem que o código interno da classe parametrizada
consiga lhes remeter.
Dessa forma, quando programa ou documenta uma classe parametrizada,
você deve listar no início da classe todas as operações que qualquer classe for-
necida como argumento real em run-time deva possuir. Por exemplo, caso sua
classe parametrizada seja ÁrvoreDeClasse, estabeleça que quaisquer classes
fornecidas (tais como Cliente ou Produto) deva ter as operações menorQue,
maiorQue, igualA, ou o que for, definida sobre elas. Isso ajudará as pessoas que
utilizam sua classe parametrizada no tocante à confirmação de que elas estão
proporcionando uma classe efetiva com o correto conjunto de operações defi-
nido sobre a mesma.
A segunda solução é prover alguma espécie de condição de guarda no iní-
cio do código da classe parametrizada. A condição de guarda verifica se a clas-
se real suprida pode reconhecer as mensagens requeridas. Infelizmente, na
maioria das linguagens orientadas a objeto da tendência dominante, isso é di-
fícil de se fazer. A linguagem Eiffel, entretanto, possui um meio engenhoso de
formar essa condição de guarda.
Em Eiffel, você pode exigir que a classe efetiva suprida a uma classe pa-
rametrizada em run-time fique dentro de um específico cone de polimorfismo.
Você realiza isso especificando a classe que será o vértice de polimorfismo.
Por exemplo, você primeiramente escreve no topo de uma classe parame-
trizada ClasseDeNó -> Imprimível, na qual Imprimível é o AOP. Isso significa
que a classe parametrizada apenas aceitará a classe Imprimível, ou uma de
suas descendentes, como a classe provida em run-time.
A seguir, você desenha uma classe chamada Imprimível com uma operação
imprimir (uma operação abstrata — ou diferida, para utilizar a terminologia
de Eiffel — porque sua implementação será definida nos descendentes de Im-
primível). Agora, uma vez que todas as pessoas que suprem uma classe para
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 327

ÁrvoreDeClasse têm de suprir uma classe descendente de Imprimível, garante-


se que classe provida tenha a operação imprimir definida sobre ela.
Observe que o nome de classe Imprimível implica que “esta classe tem ob-
jetos imprimíveis”, o que significa por sua vez que “esta classe deve ter a ope-
ração imprimir definida sobre ela”. De maneira similar, a classe Comparável
poderia ser uma com as operações menorQue, maiorQue e igualA definida sobre
ela. Essa convenção “-ável” para os nomes de classe dos AOPs de operações é
similar à convenção para os nomes de variáveis que descrevi na seção 12.2.3
(a respeito de passível de ser ligado ou aberto).
A capacidade de ser imprimida, a comparabilidade, e assim por diante,
são exemplos de aptidões de classe. A classe Imprimível na solução anterior de
Eiffel é, com efeito, uma personificação da capacidade de ser imprimida — a
“aptidão de ser imprimida”.

12.3 Resumo
A herança e o polimorfismo trazem poder e concisão ao software orientado a
objeto. Eles, de maneira idêntica, trazem perigos. O principal perigo da heran-
ça reside na utilização excessiva dela ou, mais precisamente, em sua utiliza-
ção inadequada em situações nas quais outras construções orientadas a objeto
se sairiam melhor.
Neste capítulo, examinamos os quatro abusos comuns de herança. A pri-
meira é o uso da herança em que a agregação é requerida. Esse é um erro ele-
mentar, raramente cometido pelos desenhistas experientes voltados à
orientação a objeto. O segundo abuso é a inversão da hierarquia da herança
de classe, causada muitas vezes pelo engodo de uma estrutura desencaminha-
dora do mundo real.
A terceira má utilização de herança é a confusão de classe com instância.
Isso tende a ocorrer em desenhos que precisam lidar tanto com grupos (tais
como espécies ou companhias) quanto com indivíduos (tais como animais ou
funcionários). Visto que o problema é geralmente sutil, um desenhista poderia
inicialmente fazer vistas grossas ao mesmo. Contudo, quando o desenho im-
perfeito é transformado em código, torna-se óbvio que o código não pode tra-
balhar como pretendido. A quarta utilização inadequada envolve a utilização
de herança, sendo que a transmissão de mensagens (message forwarding) pro-
veria uma construção de desenho mais apropriada.
O termo polimorfismo aplica-se tanto às operações como às variáveis.
Uma operação polimórfica é definida em diversas classes diferentes. Uma va-
riável polimórfica pode, a qualquer hora, apontar para objetos pertencentes a
328 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

classes diferentes. O escopo de polimorfismo (SOP) de uma operação é o con-


junto de classes sobre as quais é definida essa operação.
O escopo de polimorfismo de uma variável é o conjunto de classes dos ob-
jetos para os quais a variável pode, a qualquer hora, apontar durante a exe-
cução do sistema. Um escopo de polimorfismo que forma um ramo completo
de uma hierarquia de herança de classe é chamado de um cone de polimorfis-
mo (COP); a classe no topo do cone é o vértice de polimorfismo (AOP).
Para a utilização segura do polimorfismo em operações, o SOP da variá-
vel apontando para o objeto destinatário deve residir dentro do SOP da ope-
ração nomeada na mensagem. Caso um desenhista transgrida essa diretriz,
muito provavelmente ocorrerá um erro no tempo de execução (run-time).
Uma classe parametrizada é a classe que toma um nome de classe como
um argumento real quando seus objetos são gerados em run-time. O desenhis-
ta do código dentro de uma classe parametrizada geralmente não sabe qual
será a classe real suprida ao mesmo. Portanto, o SOP de quaisquer variáveis
se referindo a objetos da classe provida é muito grande. Assim, de maneira si-
milar, há a chance de que a diretriz do parágrafo anterior seja violada. Algu-
mas linguagens (tal como o Eiffel) impõem restrições sobre as classes reais
que podem ser providas e, por conseguinte, minimizam os erros em run-time
provocados pelo polimorfismo desenfreado.

12.4 Exercícios
1. No exemplo do Panda da seção 12.1.3, sugeri que você poderia fazer de es-
táEmRiscoDeExtinção: Booleano um atributo de instância (uma constante)
em EspécieNãoAmeaçadaDeExtinção e EspécieAmeaçadaDeExtinção. Mas es-
sas subclasses são realmente necessárias? Será que não conseguiríamos
dar um jeito apenas com Espécie, conforme sugeri em uma nota de rodapé
na seção 12.1.3?
2. Que modificações você teria de fazer na Figura 12.6, caso as duas classes
LinhaDeProdutoManufaturado e LinhaDeProdutoComprado estivessem so-
brepondo, em vez de decompondo, subclasses de LinhaDeProduto? (Em ou-
tras palavras, algumas linhas de produto são manufaturadas dentro da
fábrica e compradas de distribuidores externos.)
3. Como você provavelmente sabe, uma pilha do tipo “último dentro, primei-
ro fora” é uma estrutura que suporta uma série de objetos, com apenas
um deles podendo ser acessado (lido) ou removido (extraído) de cada vez.
Esse objeto é considerado o cabeça da pilha; é o objeto que foi, mais re-
centemente, agregado à (ou retirado da) pilha.
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 329

Uma lista é uma estrutura com propriedades praticamente idênticas. Co-


mente o desenho da classe Pilha, criada por herança de Lista, conforme mos-
trado na Figura 12.16. (Nesse exemplo, Lista é uma classe que suporta uma
lista individualmente vinculada, acessível apenas em uma extremidade.)

Figura 12.16 Pilha é desenhada para herdar sua implementação de Lista.

4. Será que alguns dos princípios de desenho orientado a objeto que vimos
nos capítulos anteriores são pertinentes aos problemas de herança cober-
tos neste capítulo? Em caso afirmativo, quais deles?
5. Na seção 12.2.1, quando defini o escopo de polimorfismo de uma operação,
implicitamente tratei só de hierarquias de classes de herança simples.
Que questões adicionais, se existirem, surgiriam com o SOP de uma ope-
ração caso a herança múltipla estivesse presente?
6. Suponha que uma operação op seja uma operação abstrata de uma classe
C (a qual é uma classe abstrata). Suponha além disso que op seja concre-
tamente definida sobre todas as subclasses de C, e não abstratas. Você po-
deria considerar C o vértice de polimorfismo de op, embora C.op, na
realidade, não seja implementada?
7. Suponha que uma operação op seja definida sobre uma classe A e que
seja herdada por todos os descendentes de A. Normalmente, isso signifi-
caria que A e seus descendentes formam um cone de polimorfismo para
op, com A no vértice. Mas em que situação esse grupo de classes poderia
formar um SOP imperfeito em vez de um cone completo?
330 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

8. Imagine que você pudesse fechar seus olhos e solicitar uma ferramenta
automatizada para ajudá-lo a avaliar seu desenho ou programa orientado
a objeto. Que recursos sua ferramenta poderia proporcionar para estimar
se o SOP de uma variável iria ou não cair dentro do SOP de uma operação?

12.5 Respostas
1. Nós, possivelmente, poderíamos remover a classe EspécieNãoAmeaçada-
DeExtinção. Entretanto, a classe EspécieAmeaçadaDeExtinção é necessária
se quisermos registrar, digamos, dataDoPrimeiroRiscoDeExtinção, organiza-
çãoDePreservaçãoResponsável, e assim por diante. (De forma contrária, se
esses atributos fossem definidos somente para algumas instâncias de Es-
pécie, então Espécie teria coesão de instância mista.) Se esses atributos
não forem relevantes, poderíamos trabalhar apenas com Espécie. Portan-
to, poderíamos fazer de estáEmRiscoDeExtinção um atributo de Espécie.
O valor de estáEmRiscoDeExtinção não seria constante, porque espécies
se movem para dentro e para fora do risco de extinção. Todavia, o movi-
mento de uma espécie para dentro e para fora do risco de extinção torna-
se um pouco mais difícil de desenhar se Espécie tem as subclasses
EspécieNãoAmeaçadaDeExtinção e EspécieAmeaçadaDeExtinção. Uma abor-
dagem de desenho é esta: suprima um objeto de uma subclasse (digamos,
EspécieNãoAmeaçadaDeExtinção) após salvar suas informações; então gere
um objeto da outra classe (EspécieAmeaçadaDeExtinção), que possa contar
com quaisquer informações relevantes sobre as espécies.
2. Se as duas classes LinhaDeProdutoManufaturado e LinhaDeProdutoCompra-
do se sobrepõem, então o atributo éComprado, com seus valores mutua-
mente exclusivos true e false, não mais faria sentido. De preferência,
necessitaríamos de dois atributos booleanos: éComprado e éManufaturado.
Também deveríamos acrescentar uma outra classe, denominada LinhaDe-
ProdutoManufaturadaEComprada (ou algo menos embaraçoso), que é her-
dada, por herança múltipla, de LinhaDeProdutoManufaturado, e
LinhaDeProdutoComprado. Essa nova classe geraria objetos que represen-
tariam linhas de produto que fossem tanto manufaturadas como compra-
das.
A questão de migração da resposta 1 (anterior) também se apresenta
neste caso: o que aconteceria se determinada linha de produto mudasse,
por exemplo, de comprada para manufaturada? Novamente, como na
questão anterior, poderíamos suprimir o objeto de sua subclasse e reini-
ciarmos sua geração em outra subclasse. Mas, desta vez, eu gostaria de
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 331

apresentar uma solução alternativa, que muitas vezes é conhecida segun-


do o termo dramático de “fatiamento de objetos” (object slicing).
A Figura 12.17 mostra a classe LinhaDeProduto com suas duas novas
partes, AspectoManufaturado (da classe LinhaDeProdutoManufaturado) e
AspectoComprado (da classe LinhaDeProdutoComprado). Cada uma delas
retém informação especial sobre o seu respectivo tipo de linha de produto
(manufaturado e/ou comprado). Por exemplo, LinhaDeProdutoComprado
mantém uma associação de Compras com Distribuidor.

Figura 12.17 A classe LinhaDeProduto com seus dois


aspectos opcionais como partes.

Segundo esse desenho, um objeto da classe LinhaDeProduto acessa essa


informação especial ao inquirir um atributo do aspecto mais apropriado.
Por exemplo, para confirmar o limiteDePreçoUnitário de uma linha de pro-
duto (o máximo que a gerência deseja pagar para comprar uma unidade
de uma linha de produto), um objeto utilizaria o seguinte código:
332 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

if self.éComprado
then preçoMáximo := self.aspectoComprado.limiteDePreçoUnitário
...

Essa abordagem de desenho baseia-se novamente na transmissão de


mensagens, que também utilizamos como uma alternativa para herança
do Quarto e Cubóide na seção 12.1.4. E isso conduz-nos habilmente para
a próxima resposta.
3. A herança talvez não seja a melhor abordagem nesse caso porque Lista
poderá ter um comportamento não apropriado para Pilha — por exemplo,
uma operação chamada inserirNoMeio, que não é permitida para uma pi-
lha. Os nomes das operações herdadas por Pilha também poderão não ser
muito corretos. Talvez a operação que Pilha iria denominar empurrar, se-
ria nomeada como anexar pela classe Lista. (A linguagem Eiffel capacita-o
a nomear novamente certas operações herdadas para evitar justamente
esse tipo de problema.)
Mas, para mim, o maior problema em se ter a classe Pilha herdada de Lis-
ta é minha contrariedade de ver Pilha debaixo de Lista quando navego pela
hierarquia de classe. Afinal de contas, em orientação a objeto, supõe-se
que a implementação fique escondida do observador casual.
A técnica de transmissão de mensagens (de um objeto de Pilha a um ob-
jeto de Lista) seria uma melhor abordagem para se desenhar Pilha. Con-
forme vimos na seção 12.4, a transmissão de mensagens tende a
tumultuar menos a hierarquia de classes do que a herança (especialmen-
te, se, por exemplo, você mudar mais tarde o desenho de sua classe Pilha,
para utilizar Arranjo em lugar de Lista).
4. Sim, o princípio de conformidade de tipo, abordado no Capítulo 11, é es-
pecialmente importante. Até mesmo o problema sutil envolvendo a classe
Panda, por exemplo, poderia ser estimado via conformidade de tipo. Infor-
malmente, poderíamos perguntar: pode um subtipo Panda ser provido
tanto em um contexto no qual é esperado um tipo Urso quanto em um no
qual é esperado um tipo Espécie? (Certamente que a resposta, como vimos
neste capítulo, é negativa.) Outro critério de desenho, conveniente para
estimar o problema com o quarto e o cubóide, é o da coesão de classe, dis-
cutido no Capítulo 9.
5. Um meio com herança múltipla pode ter alguns problemas tênues de de-
finição de domínios. O exemplo mais comum é o do assim denominado
clash (choque), que ocorre quando duas operações com o mesmo nome
Cap. 12 OS PERIGOS DA HERANÇA E DO POLIMORFISMO 333

(mas provenientes de classes distintas) apresentam escopos de polimor-


fismo sobrepostos. Veja a Figura 12.18.

Figura 12.18 Duas operações com o mesmo nome e


cones sobrepostos de polimorfismo.

Uma classe que herde ambas as operações estará completamente confu-


sa. Os desenhistas de linguagem surgiram com diversas soluções para de-
terminar que operações obtêm herança; a melhor dela é fazer com que a
classe criada da herança nomeie novamente as operações herdadas isen-
tas de ambigüidade.10
Na seção 8.2.2., vimos um exemplo de herança múltipla que provocou um
clash de nome em uma aplicação para aluguel de vídeos. A classe Item-
DeAluguelDePrograma herda operações de ItemDeInventárioFísico e MeioDe-
Gravação, cada um dos quais tinha uma operação obter para um atributo
denominado comprimento. (O primeiro significava comprimento da fita
em polegadas, enquanto o último significava o tempo de exibição em mi-
nutos.) Dessa forma, qualquer referência a comprimento em ItemDeAlu-
guelDePrograma é ambíguo.
Conforme sugeri no Capítulo 8, a melhor solução é dar um novo nome à
operação obter comprimento (herdada de MeioDeGravação) como extensão.
O nome de operação extensão não apenas remove a ambigüidade de com-
primento, mas, de maneira idêntica, captura a idéia do tempo de exibição.
6. Sim, na verdade considero o escopo de polimorfismo de op para formar
um cone, com C no vértice. Não interessa que C.op não tenha qualquer

10. Meyer, 1992, trata desse tópico em detalhes.


334 FUNDAMENTOS DO DESENHO ORIENTADO O OBJETO COM UML

implementação concreta, visto que C é uma classe abstrata e, de qualquer


forma, nunca terá quaisquer objetos gerados.
A resposta seria a mesma mesmo se op não fosse definida absoluta-
mente em C, contanto que C fosse abstrata e op estivesse definida em to-
das as subclasses não abstratas de C. Por exemplo, se a operação obter
área estivesse definida em todas as subclasses de Polígono, mas não na
própria classe Polígono, então o AOP de área ainda seria Polígono, desde
que Polígono fosse uma classe abstrata a partir da qual não poderiam ser
gerados quaisquer objetos.
7. O aparentemente completo COP para op seria um SOP imperfeito se al-
guns dos descendentes de A suprimisse op — vamos dizer, por exemplo,
ClasseMalComportada — seja por cancelá-la ou por dar a ela uma defini-
ção ou assinatura completamente diferente daquela em A.op. Em outras
palavras, embora em um senso literal a ClasseMalComportada.op possa
ser invocada polimorficamente, considero essa prática uma transgressão
do espírito do polimorfismo, tanto que eu deitaria fora ClasseMalCompor-
tada do âmbito do SOP de op. Essa prática iria também violar a confor-
midade de tipo.
Omitir uma operação com uma definição de operação totalmente dife-
rente é, portanto, uma prática arriscada: ela pode criar a ilusão que o
SOP de uma variável reside dentro do SOP de uma operação, embora —
por causa das lacunas no SOP das operações — isso não ocorra. Além do
mais, nas palavras do expert em orientação a objeto, Lynwood Wilson:
“Qualquer um que sobrecarregue um nome de operação dando à mesma
uma definição totalmente diferente nunca entrará no Reino dos Céus”.
8. Uma ferramenta como essa poderia proporcionar duas listas de classes.
A primeira lista conteria as classes para cujos objetos a variável aponta-
ria, e a segunda conteria as classes nas quais a operação estaria definida.
A ferramenta também poderia proporcionar uma lista de erros das clas-
ses que apareceram na primeira lista, mas não na segunda. Certamente,
existe um limite provido pela automação. Por exemplo, conforme vimos
na resposta 7 anterior, a desonestidade humana sempre pode vencer os
mecanismos ingênuos de uma ferramenta de engenharia de software.
T écnicas para organizar
operações
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
TÉCNICAS PARA ORGANIZAR OPERAÇÕES
13.Técnicas para Organizar Operações

E ste capítulo apresenta diversas técnicas de desenho para organizar os


atributos e as operações da interface de uma classe. Essas estruturas con-
tribuem muito para aumentar a robustez, a confiabilidade, a extensibilidade,
a reutilização e a manutenção das suas classes.
A primeira seção discute uma técnica de desenho orientada a objeto mui-
to útil: a utilização de classes mistas (mix-in classes) para agregar habilidades
a uma classe sem o comprometimento da coesão dessa classe. A utilização de
classes mistas também aumenta a chance de as classes desenvolvidas para
certa aplicação serem prontamente reutilizáveis em outra aplicação.
A segunda seção mostra como você pode organizar operações em anéis
concêntricos para criar uma interface dentro de uma interface e, em seguida,
fortalecer o encapsulamento, a característica mais importante da orientação a
objeto. Este capítulo aplica diversos princípios de desenho que discutimos nos
capítulos anteriores e utiliza amostras de código orientado a objeto para tor-
nar esclarecer novos conceitos de desenho.

13.1 Classes Mistas


Nesta seção, uso dois exemplos, um extraído do segmento de negócios, e outro
de gráficos, para ilustrar o conceito de classes mistas.

13.1.1 Exemplo de negócio


A fim de mostrar o que são classes mistas, e como elas podem ser úteis, vou
descrever um problema de desenho orientado a objeto extraído de uma apli-
cação referente a contas a receber na Grabbitt & Runne Enterprises, Inc. (O

335
336 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

negócio, na verdade, poderia ser qualquer um que utilizasse uma fatura sim-
ples para apresentar uma conta pela venda de vários itens.) A classe agregada
Fatura e sua classe constituinte ItemDeFatura aparecem na Figura 13.1.

Figura 13.1 Um objeto Fatura é um agregado dos objetos ItemDeFatura.

O requisito é este: os srs. Grabbitt e Runne querem enviar uma fatura


de cliente ao respectivo cliente e do modo preferido pelo mesmo. Alguns clien-
tes preferem que suas faturas sejam enviadas por fax, alguns por e-mail, e al-
guns clientes mais “saudosistas” ainda gostam de ter suas faturas entregues
pelo serviço dos Correios.
À primeira vista, esse requisito não parece ser, absolutamente, um pro-
blema de desenho. Poderíamos acrescentar, por exemplo, uma operação de fax
à classe Fatura, que permite a uma fatura “ela própria enviar um fax” a deter-
minado cliente. Mas esse desenho criaria coesão de domínio misto em Fatura
(porque ele, provavelmente, sobrecarregaria Fatura com no mínimo alguns de-
talhes de protocolo de envio de fax, os quais pertencem à arquitetura de do-
mínio). De modo prático, o desenho limitaria a reutilização e, pior, possivelmente
limitaria a reutilização da operação de fax.
Confrontados por esses problemas, poderíamos criar o desenho da Figura
13.2. Nele decompomos operações, tais como FaturaPorE-Mail e FaturaPorFax em
suas próprias classes, FaturaEnviável, que é herdeira da classe original Fatura.
Agora, Fatura pode reverter a sua forma primitiva, com coesão ideal. Para
criar uma fatura, é preferível gerarmos um objeto FaturaEnviável a um objeto
Fatura. A operação FaturaPorFax saberá como executar o fax modem e terá aces-
so às informações de Fatura (via herança) para uso no fax.
Incidentalmente, eu deveria dizer uma palavra ou duas sobre a classe
Cliente, a qual é relacionada, via uma associação de Responsabilidade, à classe
Fatura. Essa associação registra qual cliente é responsável por quais faturas.
Os atributos definidos em Cliente incluem meioComercialDePreferência (que re-
gistra a escolha de um cliente pelo meio de comunicação) e endereçoEletrônico
(o endereço eletrônico de um cliente).
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 337

Figura 13.2 A classe FaturaEnviável preserva a coesão de Fatura.

Tudo está tranqüilo, mas o desenho da Figura 13.2 ainda limita a reuti-
lização da operação de fax, a qual nós até mesmo denominamos FaturaPorFax.
Que vergonha termos toda essa tecnologia de fax modem meio escondida e in-
disponível para nós quando queremos passar fax de itens distintos de faturas,
tais como: agradecimentos, cumprimentos, ameaças e assim por diante.!
Nesse momento é que aparece uma classe mista para nos socorrer, con-
forme mostrado pela Figura 13.3.
338 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 13.3 A classe FaturaEnviável agora é criada pela herança múltipla


de Fatura e da classe mista DocumentoEnviável.

Embora a Figura 13.3 seja apenas sutilmente distinta da Figra 13.2, a di-
ferença é importante. Neste desenho, “decompomos” uma classe mista, Docu-
mentoEnviável, a qual têm toda a presteza em executar serviços de fax e de
e-mail. De forma relevante, entretanto, DocumentoEnviável não tem qualquer
conhecimento sobre faturas; ela é uma classe geral, capaz de enviar por fax
ou por e-mail qualquer documento. Portanto, agora, vamos seguir, tintim por
tintim, como o desenho integral funciona.
Quando queremos criar um objeto para representar uma nova fatura, in-
vocamos FaturaEnviável.Nova. Inicializamos esse objeto — vamos nos referir a
ele como faturaEnviável — fornecendo a este itens de fatura (e quaisquer infor-
mações de cabeçalho) e vinculando-o ao objeto Cliente responsável. Tudo isso
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 339

acontece via herança, utilizando a maquinaria de Fatura, visto que faturaEnviá-


vel pertence a uma subclasse de Fatura.
Via herança, faturaEnviável também tem disponível os recursos de comu-
nicação de DocumentoEnviável. Assim, quando queremos enviar a fatura repre-
sentada por faturaEnviável, fazemos isso em duas etapas:

1. Invocamos faturaEnviável.criarDocumento. Essa operação cria um docu-


mento de texto padrão (composto de páginas e linhas) que pode ser en-
viado por fax ou por e-mail ou pode ser impresso. Todavia, o atributo que
representa esse documento de texto (e a variável que o implementa) é de-
finido em DocumentoEnviável, não em FaturaEnviável.1 As operações que
constroem o documento (liberarDocumento e anexarTextoADocumento) são
também definidas em DocumentoEnviável. O pseudocódigo para faturaEn-
viável.criarDocumento se assemelha a algo como encontramos a seguir:2

public operation criarDocumento


begin
self.liberarDocumento; //atribuir área de texto como vazia — DE
obter o cabeçalho de fatura: // — F
converter para formato de texto o textoDeCabeçalho;
self.anexarTextoADocumento (textoDeCabeçalho) // — DE

repeat
obter a próxima linha de fatura; // — F
until não mais linhas de fatura
converter para formato de texto o textoDeLinha;
self.anexarTextoADocumento (textoDeLinha) // — DE
endrepeat

end criarDocumento;

1. Você pode utilizar o atributo doc, somente para leitura (read-only), para acessar esse docu-
mento de texto.
2. • Chave: a anotação “— DE” significa “via herança a partir de DocumentoEnviável”; a ano-
tação “— F” significa “via herança a partir de Fatura”.
• N.T.: No original, em vez de DE temos SD, e em vez de F temos I. A troca foi feita para a
compreensão ser mais imediata no caso do leitor de língua portuguesa. DE significa “documen-
to enviado e F significa “fatura”.
340 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2. Agora que preenchemos documento com as informações de fatura, preci-


samos enviá-lo, utilizando a operação FaturaEnviável.enviarParaCliente,
cujo código se assemelha a algo como encontamos a seguir:

public operation enviarParaCliente


begin
cliente:Cliente := self. clienteResponsável; // — F

case cliente.meioComercialDePreferência
“CORREIO”: self.documentoDeCorreio (cliente.nome, cliente.endereço); // —DE
“E-MAIL”: self.documentoDeE-Mail (cliente.nome, cliente.endereço eletrônico); // — DE
“FAX”: self.documentoDeFax (cliente.nome, cliente.número DeFax); // — DE

else...; //erro!
endcase;

end enviarParaCliente;

DocumentoEnviável é um exemplo de uma classe mista. Uma classe mista


geralmente suporta uma abstração ou mecanismo que poderia ser útil em di-
versas outras classes, mas que não pertence a qualquer classe particular des-
sas classes. A distribuição de abstrações e mecanismos distintos como classes
mistas melhora a reutilização dessas abstrações e mecanismos.
Normalmente, você não gera objetos a partir de classes mistas; essa é a
razão pela qual DocumentoEnviável é marcado como {abstrato}. Em lugar dis-
so, outras classes (como FaturaEnviável, neste exemplo) herdam os recursos de
uma classe mista. FaturaEnviável também é herdeira da classe Fatura, a partir
da qual um objeto da classe FaturaEnviável obtém informações específicas para
executar suas habilidades de negócio. Portanto, uma vez que uma classe mista
necessita herdar pelo menos duas superclasses, as classes mistas trabalham
melhor quando sua linguagem suporta herança múltipla.

13.1.2 Um exemplo gráfico


No caso de você detestar exemplos relativos a negócios, incluí este próximo
exemplo de classes mistas unicamente para você.
A Figura 13.4 retrata um retângulo que é livre para se mover e girar,
contanto que ele permaneça dentro de sua moldura circundante. (Eu indico os
limites de seu alcance atual na tela com linhas marcadas de topo, base, es-
querda e direita.)
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 341

Figura 13.4 Retângulo dentro de uma moldura.

A Figura 13.5 mostra parte do desenho da classe RetânguloEmMoldura,


que é herdeira de duas classes: Retângulo e FormatoEmMoldura.

Figura 13.5 A hierarquia de herança para RetânguloEmMoldura.

A classe Retângulo é a classe comum que suporta a manipulação (tal como


o mover, o girar ou o estender) de retângulos. Trata-se de uma classe que você
pode adquirir como parte de uma biblioteca de classes. FormatoEmMoldura é
menos convencional, registra o relacionamento entre um retângulo e sua mol-
dura circundante. FormatoEmMoldura é outro exemplo de uma classe mista.
342 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

No exemplo do retângulo mostrado na Figura 13.4, a classe mista Forma-


toEmMoldura oferece uma solução de desenho para um problema apresentado
pelo retângulo e pela moldura: precisamos registrar a moldura na qual um
dado retângulo está encerrado. Se modificarmos a classe Retângulo, dando-lhe
uma variável para deter essa informação, então reduziremos a reutilização de
Retângulo em outras aplicações. (Para usar os termos que apresentei no Capí-
tulo 9, sobrecarregaremos Retângulo com Moldura e daremos a ele uma coesão
de papel misto.) De qualquer forma, o distribuidor de Retângulo provavelmen-
te não nos fornecerá o código-fonte para que ele seja modificado!
Um lugar mais razoável para registrar o relacionamento de um retângulo
com sua moldura é a classe RetânguloEmMoldura, a qual tem tudo a ver com
retângulos e molduras. Isso seria muito bom, a não ser que ElipseEmMoldura
e TriânguloEmMoldura, de maneira idêntica, necessitassem de acesso ao mesmo
tipo de maquinaria. Essa é a razão de a classe mista FormatoEmMoldura ser
tão proveitosa. FormatoEmMoldura pode ser combinada com Retângulo para
produzir a classe RetânguloEmMoldura. Em outra parte do sistema, ela pode
ser combinada com Elipse para formar ElipseEmMoldura e assim por diante.
Agora, vamos examinar um pouco de código para as três classes, Retân-
gulo, FormatoEmMoldura e RetânguloEmMoldura. Veja o quadro na página 343.
A representação interna dos objetos de Retângulo fica a cargo de quatro
variáveis:

• centro registra o ponto central de um retângulo;


• altura e largura dispensam explicação;
• orientação registra o quanto um retângulo está inclinado (no sentido
anti-horário da horizontal).

Essas são as variáveis centrais de representatividade (core representatio-


nal variables) da classe; elas constituem os pilares que internamente supor-
tam a abstração externa de um retângulo. Visto que as informações que essas
variáveis provêm é parte da abstração que o Retângulo suporta, as variáveis
são também atributos públicos de Retângulo.
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 343

class Retângulo;

var centro: Ponto;


var altura, largura: Comprimento;
var orientação: Ângulo;
...
public read centro, altura, largura, orientação;
...
public operation v1 ( ): Ponto; // atributo que retorna um vértice
begin
var vértice: Ponto := Ponto.Novo;
vértice.x := centro.x + (altura * sen (orientação) + largura * cos (orientação)) / 2;
vértice.y := centro.y + (altura * cos (orientação) + largura * sen (orientação)) / 2;
return (vértice);
end v1;
...

public operation topo ( ): Comprimento; // atributo que retorna o topo


begin
return (max (self.v1.y, self.v2.y, self.v3.y, self.v4.y));
end topo;
...

public operation mover (Increm.DeMovimento: Vetor2-D); // operação


// que move o retângulo
begin
centro.x plus Increm.DeMovimento.x; // o operador plus incrementa
// a variável à esquerda
centro.y plus Increm.DeMovimento.y;
end mover;
...
endclass Retângulo;

A classe FormatoEmMoldura, da mesma forma que muitas outras classes


mistas, é simples. Ela mal contém um identificador para a moldura, isto é,
para circundar o formato, e uma alternativa booleana que registra se a mol-
dura está ativa (restringindo o retângulo) ou não.
344 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

class FormatoEmMoldura;

var molduraCircundante: Moldura; // assumir por simplicidade


// que moldura é sempre horizontal
var éAtiva: Booleano
...
public read, update molduraCircundante; //a moldura que faz o
// fechamento pode ser modificada
...
endclass FormatoEmMoldura;

Observe que RetânguloEmMoldura de certa forma se conforma a Forma-


toEmMoldura. Ou seja, um retângulo em uma moldura é um formato em uma
moldura. Entretanto, a conformidade de tipo geralmente não constitui um
problema com classes mistas verdadeiras. Isso porque uma classe mista, diga-
mos M, não tem objetos gerados pertencentes a ela. Portanto, nunca se deve
perguntar: posso prover um objeto da classe TalETal no contexto em que um ob-
jeto da classe M é esperado?
Embora uma classe mista raramente tenha objetos pertencentes a ela,
ela chega a capturar algum tipo de aspecto que oferece uma particular habi-
lidade. Utilizando herança múltipla, um desenhista pode combinar as proprie-
dades de classes mistas em uma classe, a partir da qual podem ser gerados
certos objetos. Nosso primeiro exemplo de um recurso, a capacidade de algo
poder ser imprimido (printability), surgiu na seção 12.2.4. Nessa seção, vimos
outro exemplo: a habilidade de mover-se pelo interior de uma moldura. Retor-
namos a esse tópico no exercício final do próximo capítulo, quando considera-
mos a posse de cachorros (e suas habilidades assistentes) como parte de ser
uma pessoa.

13.2 Anéis de Operações


Nesta seção, investigamos a estrutura de operações dentro de uma única clas-
se e discutimos como conseguir o máximo do encapsulamento desenhando ope-
rações em anéis internos e externos. Como exemplo, seleciono novamente
RetânguloEmMoldura (conforme mostrado na Figura 13.5), a classe que tanto
cria retângulos dentro de molduras como define o comportamento que man-
tém um retângulo dentro de sua moldura circundante. Veja aqui o código para
uma de suas operações, moverDentroDeMoldura:
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 345

class RetânguloEmMoldura;
inherits from FormatoEmMoldura, Retângulo;
...
public operation moverDentroDeMoldura (increm.DeMovimento: Vetor2-D);
begin
var increm.DeMovimentoPermitido:=Vetor2-D:=Vetor2-D.Novo; // deterá o efetivo
// movimento permitido

if self. molduraCircundante.éAtiva // molduraCircundante


// é herdeira de FormatoEmMoldura

then
if increm.DeMovimento.x > 0 // para a direita nesta convenção
then increm.DeMovimentoPermitido.x:= mín (increm.DeMovimento.x,
self.molduraCircundante.direita-self.direita);
else increm.DeMovimentoPermitido.x:= máx (increm.DeMovimento.x,
self.molduraCircundante.esquerda-self.esquerda);
endif;

if increm.DeMovimento.y > 0 // para cima nesta convenção


then increm.DeMovimentoPermitido.y := mín (increm.DeMovimento.y,
self.molduraCircundante.topo-self.topo);
else increm.DeMovimentoPermitido.y :=
máx (increm.DeMovimento.y, self.molduraCircundante.base-self.base);

endif;

else incrementoDeMovimento := incrementoDeMovimento; // não há qualquer


// moldura ativa no momento
endif;

self.mover (increm.DeMovimentoPermitido); // mover é a operação herdada


// de Retângulo

end moverDentroDeMoldura;
...
endclass RetânguloEmMoldura;

A operação moverDentroDeMoldura é uma das diversas operações que essa


classe poderia conter. (Uma outra seria girarDentroDeMoldura.) A tarefa prin-
cipal de moverDentroDeMoldura é assegurar que o retângulo não saia de sua
moldura circundante quando ele for movido em alguma direção. Para realizar
isso, a operação calcula o movimento permitido para o retângulo, o qual é o
menor movimento requerido e a menor distância até a borda da moldura (para
cada uma das dimensões x e y), e então envia uma mensagem para self. Essa
mensagem invoca a operação mover, como herdeira da classe Retângulo.
346 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Observe agora que o desenhista utiliza uma mensagem para invocar mo-
ver, em lugar de “extrair” diretamente o valor da variável centro. Mas por que
o desenhista simplesmente não extraiu centro diretamente por codificação, por
exemplo,

centro.x plus incrementoDeMovimentoPermitido.x;


centro.y plus incrementoDeMovimentoPermitido.y;

em vez de invocar mover? Afinal de contas, isso realizaria exatamente a


mesma coisa e, provavelmente, seria mais eficaz. E, embora a variável centro
seja declarada dentro de Retângulo, ela também é disponível para Retângu-
loEmMoldura, o qual é uma subclasse de Retângulo.
A resposta é encapsulamento — ou, mais especificamente, ocultação de
implementações. Invocar outra operação (normalmente uma operação obter)
do mesmo objeto, em vez de simplesmente “agarrar” uma variável diretamen-
te, é benéfico por três razões:

1. Deve evitar a duplicação de código nas duas operações.


2. Limita o conhecimento de representações de algumas variáveis para me-
nos operações.
3. Se uma das operações estiver em uma subclasse, então enviar uma men-
sagem — em vez de manipular diretamente as variáveis da superclasse
— reduz a congeneridade entre as duas classes. Por exemplo, a subclasse
não precisa conhecer tantos nomes de variáveis da superclasse (conforme
vimos no segundo exemplo na seção 8.2.5).

A Figura 13.6 mostra como a estrutura de operações poderá parecer


quando você utilizar essa abordagem de operações invocando operações no in-
terior do mesmo objeto. As operações aparecem em dois anéis.3
O anel externo compreende operações que utilizam outras operações do
mesmo objeto. As duas, operaçãoB e operaçãoC, pertencem ao anel externo por-
que enviam mensagens invocando a operaçãoD, a operaçãoE e a operaçãoF. Ob-
serve, entretanto, que os métodos de muitas operações externas acessam
diretamente, ao menos, uma variável; conforme feito pela operaçãoA.

3. Para fins de clareza, limitei minha explicação a dois anéis, mas na prática poderá haver di-
versos anéis de operação.
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 347

Figura 13.6 Anéis internos e externos de operações.

Os anéis internos compreendem operações utilizadas pelos métodos de


outras operações. Por exemplo, a operaçãoF reside no anel interior e é invoca-
da pelo método da operaçãoC com a mensagem self.operaçãoF (..., out ...) para
ler e atualizar variáveis.
Outros objetos talvez utilizem operações tanto no anel externo como no
anel interno. Expressando isso de outra forma, externo não significa público,
ao passo que interno não significa privado. Por exemplo, embora a operaçãoD
esteja localizada no anel interno, ela é acessível publicamente e utilizada pela
operaçãoA e pela operaçãoB, no anel externo.
A classe Retângulo fornece um exemplo de operações organizadas em
anéis. A operação obter topo invoca as operações obter v1, v2, v3 e v4 em vez
de fazer todos os seus cálculos diretamente a partir das variáveis centrais
(centro, altura, largura e orientação). Os desenhistas agem assim para economi-
zar código e localizar o conhecimento da representação de variáveis. (Isso sa-
tisfaz as duas primeiras razões listadas anteriormente.)
348 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Agora temos uma resposta mais completa para a questão mais antiga:
por que o desenhista da operação moverDentroDeMoldura (na classe Retângu-
loEmMoldura) não atualizou a variável centro diretamente? Devido ao perigo
de se ter operações da subclasse RetânguloEmMoldura confundindo as variá-
veis da superclasse Retângulo. (Essa é a terceira razão listada anteriomente.)
Considere o que teria acontecido se o desenhista da operação moverDen-
troDeMoldura, de fato, diretamente manipulasse o centro do retângulo, e se
nosso distribuidor de classes tivesse nos remetido uma nova versão da classe
Retângulo, versão essa que armazena os (em lugar de fazer os cálculos dos)
quatro vértices do retângulo, conforme mostrado no código a seguir.

class Retângulo; // a nova versão aperfeiçoada quanto à velocidade!

var centro; Ponto;


var altura, largura: Comprimento;
var orientação: Ângulo; // estas são as variáveis centrais de representatividade
var v1, v2, v3, v4: Ponto; // os quatro vértices do retângulo
// mantidos redundantes para
// fins de eficiência
...
public read centro, altura, largura, topo, base, esquerda, direita, v1, v2, v3, v4,
orientação;
...
public operation mover (incrementoDeMovimento: Vetor2-D);
begin
center.x plus increm.DeMovimento.x; centro.y plus increm.DeMovimento.y;
v1.x plus increm.DeMovimento.x; v1.y plus increm.DeMovimento.y; // move os
// vértices com o centro

v2.x plus increm.DeMovimento.x; v2.y plus increm.DeMovimento.y;


v3.x plus increm.DeMovimento.x; v3.y plus increm.DeMovimento.y;
v4.x plus increm.DeMovimento.x; v4.y plus increm.DeMovimento.y;
end mover;
...
endclass Retângulo;

Repare que agora a operação mover é mais complicada, pelo fato de ela
ter de manter a informação redundante retida por v1, v2, v3 e v4. (A informa-
ção é redundante porque os quatro vértices podem ser calculados a partir das
variáveis centrais de representatividade, centro, altura, largura e orientação).
Se o sistema tivesse sido simplesmente recompilado e vinculado novamente,
então a operação moverDentroDeMoldura exibiria um defeito: separaria os can-
tos de um retângulo do seu centro.
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 349

Para arrumá-lo, teríamos de reescrever o código que move os cantos. Ain-


da melhor: deveríamos reinstalar o primeiro desenho invocando a operação
mover definida em Retângulo. Em outras palavras, deveríamos dispor as ope-
rações de Retângulo sob a forma de anéis.4

13.3 Resumo
Este capítulo tratou da organização e do desenho de operações. A primeira
abordagem de desenho que exploramos utilizou classes mistas para livrar-se
de outras classes de abstrações que não pertencem a suas interfaces. Vimos
que uma classe mista é uma construção relativamente simples, normalmente
abstrata. Assim, um desenhista utiliza a abstração ou o mecanismo que a clas-
se mista personifica, via herança, para criar uma nova classe de combinação.
Esta nova classe, com suas diversas avenidas de herança, pode então possuir
abstrações gerais (digamos, do domínio de negócio) e abstrações mais especí-
ficas (digamos, do domínio de arquitetura).
Ao restabelecer abstrações restritivas de uma classe de negócio em uma
classe mista, um desenhista aperfeiçoa a coesão da classe de negócio, o grau
de dependência e a reutilização. Visto que a mesma classe mista talvez possa
ser útil em diversas situações de desenho, pode-se eliminar a presença de có-
digo supérfluo nas aplicações e nas bibliotecas de classes. A reutilização das
habilidades da classe mista também é melhorada.
A segunda abordagem de desenho neste capítulo tratou da organização
de operações em anéis para criar camadas de encapsulamento dentro de uma
única classe. Essa abordagem utiliza a ocultação de informações e implemen-
tações nas operações do “anel interno” para proteger as operações do “anel ex-
terno” do conhecimento desnecessário da forma como as variáveis são
implementadas. Por conseguinte, se o desenhista tivesse que modificar, diga-
mos, os nomes, classes ou outros detalhes de certas variáveis, menos opera-
ções precisariam ser escritas de novo.

13.4 Exercícios
1. No desenho da Figura 13.3, a classe FaturaEnviável apresenta coesão de
domínio misto, porque ela conhece faturas (do domínio de negócio) e co-
municação (do domínio de arquitetura). Essa coesão de domínio misto re-
presenta algum problema para esse desenho?

4. Retorno a esse exemplo e ao assunto de desenho para fins de eficiência no exercício 4, no final
deste capítulo.
350 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

2. No desenho de RetânguloEmMoldura mostrado na Figura 13.5, por que o


desenhista simplesmente não se contentou com RetânguloEmMoldura cria-
do por herança múltipla a partir de Moldura e Retângulo, em vez de in-
troduzir a classe FormatoEmMoldura?
3. Verifique novamente o desenho do Quarto (na seção 12.1.4, Figura 12.9),
que utilizou transmissão de mensagens, e o desenho de RetânguloEmMol-
dura (na seção 13.1.2, Figura 13.5), que utilizou herança múltipla. O que
aconteceria se você trocasse as abordagens utilizadas para desenhar es-
sas duas classes? Em outras palavras, como você poderia abordar o dese-
nho do problema do “volume do quarto” utilizando a herança múltipla?
4. De maneira inversa, como você desenharia uma solução para o problema
dos “retângulos em molduras” utilizando a transmissão de mensagens?
5. Por que o desenho da classe Retângulo (primeira versão, na seção 13.1.2)
poderia ocasionar ineficiências em run-time? (Sugestão: Considere a de-
claração local var vértice := Ponto.Novo; dentro da operação v1, e observe
que v1 retorna vértice como seu resultado.)

13.5 Respostas
1. A coesão de domínio misto da classe FaturaEnviável não chega a ser na
realidade um problema, porque não esperamos reutilização de FaturaEn-
viável. Essa classe está lá apenas para “sofrer as conseqüências”, sendo
sua tarefa aperfeiçoar a reutilização de DocumentoEnviável e Fatura.
2. A razão é fácil de ser compreendida: um retângulo não é uma moldura.
(Coincidentemente, entretanto, as molduras nessa aplicação eram retân-
gulos.) Se estivéssemos prestes a capacitar Retângulo como herdeira de
Moldura, então violaríamos o princípio de conformidade de tipo que foi
discutido no Capítulo 11.
3. A Figura 13.7 mostra QuartoCubóide, desenhado de forma a ser herdeiro,
por herança múltipla, de Cubóide e Quarto.
Esse desenho trabalhará para que os objetos da classe QuartoCubóide enten-
dam a mensagem volume (ou obterVolume, se você preferir esse estilo). Eles exe-
cutarão a operação volume definida em Cubóide (em que, presumivelmente,
comprimento, largura e altura também residem.) Entretanto, como vimos no Ca-
pítulo 12, esse desenho implica que QuartoCubóide herde um comportamento des-
necessário de Cubóide (girar, por exemplo). Para evitar que alguém utilize girar
em um quarto cubóide, o desenhista de QuartoCubóide deverá suprimir essa ope-
ração (e quaisquer outras que forem desnecessárias ou perigosas).
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 351

Figura 13.7 QuartoCubóide herdado, por herança múltipla, de Cubóide e Quarto.

Outro problema com esse desenho é que você não pode substituir a classe
Cubóide por Formato3D, cujas subclasses incluem Cubóide, Cilindro e assim por
diante. O melhor modo de entender o porquê de não se poder utilizá-la é ima-
ginar que o desenhista de Formato3D tenha feito de volume uma operação abs-
trata, o que seria muito plausível. Agora, uma subclasse como um
QuartoDeFormato3D herdaria uma operação volume sem qualquer implemen-
tação. O que não é de muita utilidade!
Certamente, ter QuartoCubóide herdado de Cubóide viola o princípio da
conformidade de tipo. Você poderia afastar todas essas dificuldades tendo uma
classe como Quarto3D herdada de algo parecido com FormatoFixado3D e Quar-
to.5 Sucessivamente, a classe FormatoFixado3D teria uma variável que se refe-
re a um objeto da classe Formato3D. Isso é exatamente análogo ao desenho de
RetânguloEmMoldura da Figura 13.5, mas trata-se de algo demasiadamente
elaborado quando comparado ao desenho do Quarto da Figura 12.9.
4. A seguir temos o código para uma das operações de RetânguloEmMol-
dura, moverDentroDeMoldura, que, agora, move o retângulo dentro da
moldura circundante por meio de transmissão de mensagens para um
objeto Retângulo referido como retângulo, em vez de por via herança a
partir de Retângulo (como no desenho mostrado na Figura 13.5).

5. FormatoFixado3D trata-se de uma classe que se assemelha a Formato3D, mas carece (por
exemplo) de uma operação escala.
352 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

class RetânguloEmMoldura;
...
var retângulo:Retângulo; // será inicializada para apontar para o retângulo movível
var molduraCircundante: Moldura // será inicializada para apontar para a moldura
// circundante
...
public operation moverDentroDeMoldura (incrementoDeMovimento: Vetor-2D);
begin

var increm.DeMovimentoPermitido: Vetor-2D := Vetor-2D.Novo; // deterá o real


//movimento
// permitido
if molduraCircundante.éAtivo
then
if increm.DeMovimento.x > 0 // para a direita nesta convenção
then increm.DeMovimentoPermitido.x := mín (increm.DeMovimento.x,
molduraCircundante.direita-self.direita);
else increm.DeMovimentoPermitido.x := máx (increm.DeMovimento.x,
molduraCircundante.esquerda-self.esquerda);
endif;

if increm.DeMovimento.y > 0 // para cima nesta convenção


then increm.DeMovimentoPermitido.y := mín (increm.DeMovimento.y,
molduraCircundante.topo-self.topo);
else increm.DeMovimentoPermitido.y := máx (increm.DeMovimento.y,
molduraCircundante.base-self.base);
endif;
else increm.DeMovimentoPermitido := increm.DeMovimento; // não há moldura ativa
// no momento
endif;

retângulo.mover (increm.DeMovimentoPermitido); // transmissão de mensagens


end moverDentroDeMoldura;
...
endclass RetânguloEmMoldura;

Esse desenho tem muitas similaridades com o desenho de Retângu-


loEmMoldura mostrado na Figura 13.5 (e, como código, na seção 13.2). A
diferença crucial, naturalmente, é que agora RetânguloEmMoldura não é
herdeira das classes FormatoEmMoldura e Retângulo. Em lugar disso, te-
mos duas variáveis (retângulo e molduraCircundante) que apontam, res-
pectivamente, para o retângulo móvel e sua moldura circundante.
Esse desenho conserva a vantagem mais importante do desenho mos-
trado na Figura 13.5, visto que a classe Retângulo não é sobrecarregada
com a classe Moldura. Todavia, nesse desenho, as operações de Retângulo
Cap. 13 TÉCNICAS PARA ORGANIZAR OPERAÇÕES 353

não são automaticamente disponíveis para os objetos da classe Retângu-


loEmMoldura. O desenhista de RetânguloEmMoldura deve explicitamente
reproduzir diversas operações de Retângulo (tal como girar) em Retângu-
loEmMoldura. Embora a implementação dessas operações sejam triviais
(porque cada uma delas será uma simples transmissão de mensagem
para o objeto retângulo na forma retângulo.girar, por exemplo), essa tarefa
será tediosa. Além disso, o desenho de RetânguloEmMoldura talvez neces-
site ser modificado sempre que uma nova operação for acrescentada a Re-
tângulo.
5. Para responder a essa questão de uma maneira bem específica, assu-
mirei que variáveis locais — ou seja, variáveis declaradas dentro do
método de uma operação, da forma como vértice foi declarada dentro do
método de v1 na seção 13.1.2 — são colocadas na pilha do computador e re-
movidas assim que a operação finaliza. Isso normalmente significa que os
objetos referenciados unicamente por variáveis locais são “varridos” pelo “co-
letor de lixo” quando a operação termina. (A seção 1.4 mencionou a coleção
de lixo de objetos não mais acessíveis.) Assim, os objetos referenciados so-
mente dentro de uma única operação não consomem memória durante mui-
to tempo; eles não possuem mais do que uma vida bem efêmera.
Entretanto, no primeiro desenho de Retângulo (veja o código na seção
13.1.2), o objeto referido por vértice não pode simplesmente desaparecer
quando a operação obter v1 termina. Na verdade, esse objeto deve ser pre-
servado quando a operação termina, porque o que precisamente v1 retor-
na é um identificador daquele objeto. Daí, infelizmente, se v1 for
invocado, digamos, 100 vezes, a memória ficará preenchida com cerca de
60 objetos. (Alguns objetos talvez tenham sido “coletados pelo lixeiro” nes-
se ínterim.) Isso poderia representar um uso bastante ineficiente do es-
paço, e constituir, igualmente, um processo meio lento.
O segundo desenho de Retângulo (veja o código no início da seção 13.2)
evita a criação local repetida de um objeto dentro de uma operação ao
manter v1 como uma variável de instância do próprio objeto integral. En-
tretanto, esse desenho deve certificar-se de que v1 seja sempre atualiza-
do, ao atualizá-lo obsessivamente todas as vezes que um retângulo se
move. Isso cria um bocado de código extra. Um desenho com um bom
comprometimento se situaria a meio do caminho entre os dois desenhos:
atualizaria v1 só quando alguém pedisse por isso e, em seguida, retorna-
ria um identificador para v1 em vez de retornar um identificador para al-
gum novo objeto que acabaria ocupando espaço na memória.
354 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Lembre-se de que, uma vez que linguagens, compiladores e ambientes


em run-time variam realmente bastante, o código da linguagem de seu lo-
cal de trabalho talvez se comporte diferentemente do que o exposto aqui
em minha descrição. Entretanto, o caso geral ainda é apropriado: durante
o desenho, às vezes vale a pena considerar o que acontecerá de fato na
máquina. Ao proceder dessa forma, você poderá manter seu desenho livre
de complicações e seu código eficiente. Isso é o que objetivo nesse terceiro
desenho (compromisso).
Todavia, não crie suas eficácias de desenho em torno de algum sub-
terfúgio da Versão 2.3.1.2.6 de seu compilador, porque quando a Versão
2.3.1.2.7 tornar-se pública, você poderá ficar arruinado. Para a maioria
dos códigos, a portabilidade é mais importante do que a eficiência.
C oesão de classe, suporte de estados
e de comportamentos
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS
14.Coesão de Classe, Suporte de Estados e de Comportamentos

N este capítulo, discuto a qualidade da interface de uma classe — a natu-


reza de sua “face externa”. Essa qualidade depende não somente da coe-
são de classe, a qual abordamos no Capítulo 9, mas também da organização e
do desenho de suas operações. Em essência, este capítulo responde duas ques-
tões: o que faz de uma classe ser uma boa concepção de um tipo de dado abs-
trato? o que faz com que uma classe não alcance esse ideal?
Parte da resposta para essas questões envolve os desenhos do espaço-es-
tado e do comportamento de uma classe, que examinamos no Capítulo 10. Vol-
to a esses conceitos para distinguir os vários modos pelos quais o espaço-
estado e o comportamento podem ser acessados por meio da interface de clas-
se, e para demonstrar como o desenho das operações individuais pode afetar
a qualidade de toda a interface de uma classe.
A primeira seção do capítulo discute como uma interface pode suportar o
espaço-estado de uma classe. A segunda seção discute como uma interface
pode suportar o comportamento de uma classe. A seção final trata da coesão
de operações individuais em uma interface. Durante todo este capítulo, utilizo
como exemplos a classe Retângulo do Capítulo 13 e uma classe PedidoDeCliente
de uma aplicação referente à entrada de pedidos na Grabbit & Runne Enter-
prises, Inc.
Este capítulo completa nossa jornada pelos fatores de desenho que deter-
minam a qualidade de uma aplicação orientada a objeto: sua robustez, confia-
bilidade, extensibilidade, reutilização e manutenção. Os exercícios no final do
capítulo também procuram reunir muitas das idéias de desenho que explora-
mos nos capítulos anteriores.

355
356 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

14.1 Suporte de Estados em uma Interface de Classe


Conforme vimos no Capítulo 10, um objeto, durante sua existência, move-se
de estado para estado dentro do espaço-estado de sua classe. As mensagens
que um objeto recebe o conduzem de um estado a outro.
Você talvez esperasse que um objeto pudesse ser unicamente levado para
os estados válidos do espaço-estado de sua classe. Embora isso seja verdadeiro
quando se tem um bom desenho de interface de classe, receio que não corres-
ponda à realidade em todos os desenhos. Nesta seção, analiso quatro tipos de
desenho de interface de classe e ressalto as deficiências (se houver) de cada
um deles no tocante ao suporte do espaço-estado de uma classe.
1. Estados ilegais
Uma interface de classe que permita estados ilegais, habilita a um objeto
o alcance de estados que violam a invariante de classe desse objeto. Por
exemplo, considere uma operação moverPonto definida sobre Retângulo,
permitindo a um único vértice de um retângulo se mover independente-
mente dos demais vértices. Isso é praticamente o mesmo que distorcer
um retângulo em um trapezóide. Em outras palavras, um retângulo po-
deria tornar-se um objeto distinto de retângulo, fato esse que violaria sua
invariante de classe.
Uma interface que permita a um objeto atingir estados ilegais repre-
senta um desenho pobre, sem recursos. Isso normalmente ocorre quando
um desenhista revela uma parte da implementação interna da classe.
(Eu, certa vez, ouvi alguém descrevendo esse tipo de interface como “per-
mitindo que a implementação vazasse para fora”.) Neste exemplo, tería-
mos imaginado que a classe Retângulo fosse implementada internamente
por quatro variáveis, que se referem aos quatro vértices de um retângulo.
Entretanto, ao expor esses vértices sem quaisquer restrições, o desenhis-
ta permitiu que um objeto Retângulo caísse dentro de certos estados ile-
gais.
No pior tipo de desenho, pode-se permitir a um objeto que este atinja
todos os estados possíveis para sua implementação, muitos dos quais po-
deriam ser ilegais. Por exemplo, um retângulo implementado por inter-
médio de linhas para seus lados, e cada uma dessas linhas diretamente
manipulável a partir do lado externo do objeto, poderia resultar em qua-
tro linhas desconectadas no final.
2. Estados incompletos
Em um desenho de interface de classe com estados incompletos, existem
estados válidos no espaço-estado de Retângulo que um objeto não conse-
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 357

gue alcançar. Por exemplo, vamos supor que, devido ao desenho pobre da
classe Retângulo, todos os retângulos deverão apresentar larguras maio-
res do que suas respectivas alturas. Em outras palavras, não poderiam
ser criados retângulos com formato quadrado, e nem retângulos “altos”.
De acordo com minha experiência, esse tipo de imperfeição de interfa-
ce de desenho ocorre menos freqüentemente do que a imperfeição de in-
terface devido a estados ilegais. Mas, observe que uma interface pode ter
um problema duplo: ela é capaz de suportar estados ilegais e estados in-
completos.
3. Estados inapropriados
Um desenho de interface de classe com estados inapropriados normal-
mente propicia aos usuários do lado de fora de um objeto alguns estados
que não são formalmente partes da abstração de classe do objeto. Por
exemplo, suponha que um desenhista tenha criado uma classe Pilha desse
tipo (“último dentro”, “primeiro fora”). Ele implementou a pilha por meio
de um arranjo e de um identificador de arranjo. Até o momento, sem pro-
blemas. Mas o que aconteceria se, agora, fizéssemos com que o identifi-
cador de arranjo fosse publicamente visível? Dessa forma, poderíamos
dizer que ele criou uma interface com estados inapropriados, pois um
identificador de arranjo não constitui parte de uma abstração de pilha.
(Os usuários de uma pilha deveriam ver somente o último elemento, e
identificar se a pilha está vazia ou cheia.) Como outro exemplo desse tipo
de transgressão de interface, o desenhista pode permitir aos usuários de
uma pilha verificar, por exemplo, o décimo sétimo elemento dela.
(Aqui, estou assumindo que o desenhista não vá permitir aos usuários
da pilha efetivamente alterar o identificador de arranjo ou o décimo séti-
mo elemento. Se isso fosse permitido, conseqüentemente o desenhista te-
ria criado uma interface que suportasse estados ilegais. Por exemplo, um
usuário poderia atribuir ao identificador de arranjo um número negativo
ou um número extremamente grande.)
Entretanto, a questão dos estados inapropriados torna-se algo um tan-
to intrincado em muitos projetos. Por exemplo, a profundidade de uma
pilha constitui uma informação que deve ser levada a conhecimento pú-
blico? A maioria das pessoas que estuda o conceito de pilhas iria respon-
der. “Não, somente o topo de uma pilha é relevante”. Entretanto,
considere uma classe Fila do tipo (“primeiro dentro”, “primeiro fora”). Nes-
se caso, muitos desenhistas considerariam a extensão atual de uma fila
altamente relevante para o usuário da mesma.
358 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Como se pode concluir desses exemplos, você provavelmente ainda


terá de acompanhar algumas discussões acaloradas sobre seu próximo
projeto sobre o que é apropriado e o que é inadequado. Entretanto, espero
que, ao contemplar a questão dos estados inapropriados, você possa evi-
tar algumas das interfaces de desenho que tenho visto recentemente, nas
quais partes ao acaso e potencialmente alteráveis de implementações in-
ternas de classes foram inadvertidamente reveladas para o mundo.
4. Estados ideais
Uma interface de classe com estados ideais, como o nome sugere, trata-se
do melhor desenho para uma interface de classe. Um objeto de uma clas-
se pode atingir qualquer estado válido para essa classe, mas somente os
estados válidos para a classe. Obviamente, o conhecimento de quais esta-
dos são válidos, e quais são ilegais, depende de se ter um bom entendi-
mento da finalidade da classe e de uma definição de sua invariante de
classe (um tópico para o qual retorno a seguir, na seção sobre comporta-
mento).

14.2 Suporte de Comportamentos em uma Interface de


Classe
Um objeto apresenta algum tipo de comportamento quando ele se move de seu
estado atual para outro (ou, às vezes, para o mesmo estado) como resultado
do recebimento de uma mensagem. A interface de uma classe pode ser pessi-
mamente desenhada, de forma que ela possa suportar um comportamento ile-
gal ou nem mesmo chegar a dar suporte a um comportamento válido. A
seguir, relaciono e explico os sete modos pelos quais um desenhista pode cons-
truir uma interface de classe para suportar — ou não — comportamento de
uma forma apropriada. A maioria destes modos tem deficiências específicas,
as quais descrevo igualmente.
1. Comportamento ilegal
Uma interface de classe que suporta comportamento ilegal tem uma ope-
ração que permite a um objeto fazer transições ilegais de um estado para
outro. Por exemplo, digamos que um pedido de cliente deva ser aprovado
antes de ele ser cumprido. Se um objeto da classe PedidoDeCliente puder
ir diretamente de um estado de não aprovado a cumprido, por intermédio
de alguma operação provida na interface, então a interface suportará
comportamento ilegal.
Observe que aqui é o comportamento que é ilegal — não os dois esta-
dos envolvidos, cada qual constituindo um estado válido para um pedido
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 359

de cliente. O desenhista dessa classe não ofereceu suporte ao modelo re-


querido de transição de estado para PedidoDeCliente, provavelmente por-
que permitiu que PedidoDeCliente.statusDeCumprimento fosse manipulado
diretamente por meio da interface.
Há muitos outros exemplos, freqüentemente mais sutis, de interfaces
que suportam comportamento ilegal. Recentemente vi um desenho da
classe Pilha em que um usuário de um objeto pilha poderia extrair um ele-
mento do meio desta (digamos, o décimo nono) e, então, agrupar as duas
partes separadas da pilha para, novamente, edificar uma pilha integral.
Percival, o desenhista, tinha deixado a abstração da pilha se partir em
pedaços, o que me irritaria sobremaneira se eu tivesse de considerar a
utilização dessa classe Pilha vinda de uma biblioteca de classes.1
Você muito provavelmente seria capaz de deduzir do exercício 3 no fi-
nal do Capítulo 12, qual o motivo de o desenhista ter oferecido suporte a
esse comportamento ilegal em sua interface de Pilha. A resposta é que ele
capacitou Pilha para ser herdeira de Lista, que legitimamente permite a
remoção de um elemento do meio, e não suprimiu o comportamento ofen-
sivo dentro de Pilha. Na realidade, ele provavelmente deveria ter nomea-
do a classe dele de ListaDePercival, um nome mais honesto do que Pilha
para esse caso.
Novamente, da mesma forma que no caso dos estados inapropriados,
decidir qual comportamento é ilegal e qual é válido para uma dada classe
poderá envolver alguns estudos do projeto. Todavia, você sempre deveria
esquadrinhar e eliminar a possibilidade de casos de comportamento ilegal
que resultarem de um uso excessivamente entusiasta da herança.
2. Comportamento perigoso
Quando uma classe tem uma interface com comportamento perigoso, são
necessárias várias mensagens para efetivar uma única etapa de compor-
tamento de um objeto, e pelo menos uma das mensagens leva o objeto
para um estado ilegal. (Conseqüentemente, uma interface de classe com
comportamento perigoso deve também permitir a um objeto atingir esta-
dos ilegais, conforme o primeiro desenho de interface isento de recursos
descrito na seção 14.1.)
Muitas interfaces com comportamento perigoso que vi eram bizarras.
Veja aqui um exemplo: digamos que o pedido de um cliente é aprovado
neste momento, mas acontece de todas as linhas de produto solicitadas

1. Percival na realidade tinha uma classe Pilha perfeitamente boa na biblioteca de seu local de
trabalho. Entretanto, deixou de utilizá-la, porque, de maneira acertada, ela não suportou o
tipo de travessuras ruidosas que ele pensava ser necessário.
360 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

no pedido estarem em falta no estoque. (Eu estou assumindo que todas


elas estão faltando no estoque por simplicidade.) Portanto, precisamos
dar ao pedido um estado de atrasoNoCumprimento.
Se a interface de PedidoDeCliente é desenhada com comportamento pe-
rigoso, então a alteração do estado de um pedido talvez não seja algo fá-
cil. Por exemplo, talvez o único meio de efetuarmos a transição de
aprovado para atrasoNoCumprimento seja esta:

• Primeiro, envie uma mensagem para um pedido que fixe o número de


itens encomendados sob a forma de um número negativo. Esse é o es-
tado ilegal bizarro.
• Segundo, envie uma mensagem informando ao pedido que ele cumpra
sua função em seus próprios termos. O código dentro de PedidoDe-
Cliente.efetivar, em seguida, fixa o número de itens em atraso sob a
forma de um número positivo e atribui atrasoNoCumprimento para o
estado.

Essa interface, portanto, requer duas mensagens para se atingir um re-


sultado; a primeira delas, colocando o objeto em um estado ilegal.
A classe Retângulo pode prover outro exemplo referente a esta espécie
de interface — um exemplo tão horrível como esse, mas não tão esquisito.
Digamos que desejássemos mover um retângulo para a direita. Para fa-
zer isso, em certo desenho reprovável de Retângulo, temos de enviar qua-
tro mensagens ao objeto retângulo porque cada mensagem move um
vértice. À medida que o retângulo hesita em ir para à direita, ele termina
ficando em dois ou três estados ilegais intermediários. (Isso, é certo, foi
também o exemplo de uma interface com estados ilegais que utilizei na
seção 14.1.)
Uma interface que suporta comportamento perigoso inspira uma “vida
infernal” para uma interface de estados ilegais porque ela encoraja — o
melhor, obriga — as pessoas a posicionarem um objeto dentro de estados
ilegais. Isso, mais adiante, não apenas deixa a descoberto uma implemen-
tação passível de sofrer mudanças, como também aumenta o risco de um
objeto ser deixado em algum estado ilegal. Repare, de maneira idêntica,
como uma interface de comportamento perigoso igualmente promove con-
generidade de algoritmo pelas fronteiras de classe porque os usuários da
classe Retângulo precisam conhecer o algoritmo pelo qual um retângulo é
movido (a saber, um vértice de cada vez).
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 361

3. Comportamento irrelevante
O comportamento irrelevante em uma interface de classe é o comporta-
mento que simplesmente não pertence a essa classe e a seus objetos. Por
exemplo, se PedidoDeCliente contivesse uma operação denominada calcu-
larRestituiçãoDeEmpréstimo, conseqüentemente PedidoDeCliente teria com-
portamento irrelevante. O comportamento não é relevante a essa classe,
porque ele não tem qualquer relação com pedidos de clientes: não atuali-
za qualquer objeto PedidoDeCliente e nem mesmo acessa quaisquer variá-
veis de PedidoDeCliente.
Não, você não entendeu mal; incluir comportamento irrelevante em
uma interface corresponde a um desenho estúpido. Afortunadamente
para o mundo orientado a objeto, isso raramente ocorre. O grande repre-
sentante do comportamento irrelevante é um camarada notoriamente fal-
so, chamado Genghis, o Perverso, que trabalha em uma grande
companhia muito distante de sua empresa (Eu espero!). Genghis, com um
certo contentamento, colocaria uma operação calcularDiferençaEntreDatas
na interface de Cliente e determinarMelhorRotaDeTransporte na interface
de Produto. Não me perguntem o motivo. Também não faz qualquer sen-
tido para mim. Pergunte ao Genghis — acho que ele saberá responder!
4. Comportamento incompleto
Uma interface de classe com comportamento incompleto não permite todo
o comportamento que deveria ser posto em prática por objetos dessa clas-
se. Por exemplo, vamos assumir que um pedido de cliente tenha o estado
de aprovado, mas que esse cliente, repentinamente, vá à falência. É in-
teiramente razoável que os usuários do departamento de contabilidade
mudem o estado do pedido mais uma vez, agora para não aprovado. Mas,
em um sistema que revisei recentemente, vi PedidoDeCliente desenhado
de forma tal que, uma vez que um pedido tivesse atingido um estado de
aprovado, não haveria qualquer meio possível de retorná-lo para o estado
de não aprovado. (O desenhista tinha simplesmente ignorado um dos re-
quisitos de análise.)
Com o comportamento incompleto, não temos um caso de uma inter-
face suportando comportamento de forma desastrada ou via estados ile-
gais. Uma interface desse tipo efetivamente veta qualquer
comportamento válido, pelo fato de que nem todas de transições válidas
entre esses estados são suportadas. Observe, entretanto, que uma classe
com uma interface que suporta comportamento incompleto pode ainda
suportar estados ideais, porque (no exemplo anterior) um pedido poderia,
de qualquer forma, ser capaz de atingir um estado de não aprovado; o pro-
362 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

blema apenas é que ele não poderia atingir esse estado vindo de outro,
aprovado.
5. Comportamento inábil
Um objeto cuja classe tenha uma interface com comportamento inábil
pode requerer duas ou mais mensagens para pôr em prática uma única
etapa de comportamento válido. O comportamento inábil se assemelha ao
comportamento perigoso (descrito anteriormente), pois naquele são ne-
cessárias várias mensagens para efetivar um único comportamento de
um objeto. Entretanto, com o comportamento inábil, nenhuma das men-
sagens conduz o objeto a um estado ilegal.
Por exemplo, vamos dizer que um pedido de cliente com um estado de
aprovado pode tornar-se cumprido quando ele tiver estoque e uma data de
remessa atribuídos ao mesmo. É perfeitamente razoável que um pedido
cumprido tenha sua data de remessa alterada. Entretanto, uma interface
poderá ser desenhada de forma que o único meio de se fazer isso seja es-
pecificar o estado do pedido de volta a aprovado, e em seguida restabele-
cê-lo para efetivado com a nova data de remessa. Você, portanto, deverá
enviar duas mensagens a fim de modificar uma data de remessa.
Repare que o objeto passa por meio de um estado adulterado, se bem
que válido. O estado é adulterado porque ele não corresponde à realidade:
agora, o pedido em questão é efetivado e não mais meramente aprovado.
O desenhista deixou de suportar o comportamento requerido para a alte-
ração da data de remessa. Ou, para ser justo com o desenhista, ele pode
ter deixado de suportar o comportamento requerido porque não foi entre-
gue a ele a especificação completa referente a PedidoDeCliente.
Entretanto, desenhar uma interface com comportamento inábil não
chega a ser um pecado capital. De fato, nem sempre fica suficientemente
claro se um objeto deveria ser capaz de mover-se de um estado para outro
em uma única etapa. Por exemplo, seríamos capazes de mover um retân-
gulo para a direita e girá-lo 30o com uma única mensagem, ou isso deve-
ria ser considerado duas etapas de comportamento? Ou, se pudermos
reduzir de novo a escala de um retângulo, com seu centro posicionado no
mesmo lugar, a interface de Retângulo suportaria igualmente essa nova
redução de escala com um vértice mantido no lugar? (Afinal de contas,
tudo isso poderia ser feito invocando-se reduzirDeNovoEscalaNoCentro e
em seguida mover.)
A melhor maneira de responder a essas questões é estudar as neces-
sidades do problema e visualizar como a classe pretende ser utilizada. Se
você puder prever o futuro de maneira infalível, sempre obterá as respos-
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 363

tas absolutamente corretas. (Mas, se você consegue prever o futuro sem


cometer erros, o que está fazendo no ramo de software?)
Minha recomendação geral seria esta: não prover uma operação para
suportar uma etapa especulativa de comportamento, se esse comporta-
mento já consegue ser suportado pela execução de duas ou três operações.
Espere até que realmente surja a necessidade pelo comportamento antes
de considerar a agregação de outra operação para a interface. (Esse tópi-
co aparece inesperadamente de novo na listagem a seguir, segundo o
comportamento replicado.)
Embora eu apenas tenha mencionado “as necessidades do problema”,
não elaborei essa frase aleatoriamente. Isso é deliberado, pois apreciar to-
dos os usos possíveis para uma classe é uma questão de puro julgamento
humano. Essa é a razão pela qual a sua experiência como desenhista
orientado a objeto sempre será valiosa. Essa também é a razão pela qual
um ensaio (ou qualquer forma de revisão realizada por grupos de compa-
nheiros de trabalho) é vital no desenho orientado a objeto, pois raramente
uma pessoa sozinha consegue estimar todas as sutilezas — e as poten-
ciais alterações futuras — de uma dada aplicação ou classe.
6. Comportamento replicado
Uma interface de uma classe tem comportamento replicado se a mesma
etapa de comportamento em um objeto puder ser efetivada, via interface
desse objeto, de mais de um modo. Os desenhos de classe que tenho visto
contêm incontáveis exemplos de comportamento replicado. Deixe-me dar-
lhe uma amostra representativa para ilustrar os vários casos de (e as ra-
zões para) comportamento replicado.
Recorde-se da classe Hominóide do Capítulo 1. Essa classe tem duas
operações, virarÀEsquerda e virarÀDireita, que viram um hominóide de 90o,
respectivamente, para à esquerda ou para à direita. Agora, digamos que
precisamos desviar um hominóide de, por exemplo, 30o no sentido horário
(para a direita, conforme visto). Para realizar isso, escrevemos outra ope-
ração, virar, que toma um argumento de ânguloDeRotação. Acrescentando
essa operação, criamos o comportamento replicado na interface de Homi-
nóide, visto que, agora, podemos virar um hominóide a 90o sob dois modos
distintos:

virarÀDireita; // primeiro modo


virarNoSentidoHorário(ânguloReto); // segundo modo
364 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Essa etapa de comportamento replicado surgiu por razões históricas, es-


pecificamente porque generalizamos a interface original de hominóide. Caso
tivéssemos inicialmente previsto a necessidade de “viradas” arbitrárias, ja-
mais poderíamos ter escrito virarÀEsquerda e virarÀDireita. Mas, agora que te-
mos essas duas operações, o que vamos fazer? Não podemos simplesmente
removê-las, pelo fato de que o código em diversas outras classes talvez esteja
se referindo a elas.
Surgem duas soluções possíveis:

• Deixe-as em paz, e viva com o comportamento replicado de Hominóide


e sua interface resultante mais complexa.
• Remova as duas operações depois de mais ou menos um ano, período
esse em que o outro software poderá ser modificado para utilizar, no
seu lugar, virarNoSentidoHorário (ânguloReto).

Ironicamente, às vezes uma classe pode evoluir de maneira oposta ao


modo descrito, pois o seu desenhista poderá introduzir deliberadamente
comportamento replicado em sua interface. Por exemplo, digamos que
atualmente Hominóide tenha apenas a operação geral virarNoSentidoHorá-
rio como uma operação destinada a virá-lo. Entretanto, vamos igualmen-
te supor que 99% das aplicações que precisam virar um hominóide
necessitam fazê-lo segundo um ângulo reto. O desenhista, conseqüente-
mente, poderia acrescentar virarÀEsquerda e virarÀDireita como uma como-
didade para muita gente que então não mais teria de se lembrar se é à
esquerda ou à direita que um ângulo negativo ou um movimento no sen-
tido anti-horário. Certamente que ele terá de deixar virarNoSentidoHorário
na interface para o 1% restante, que criará o comportamento replicado.2
Uma variação sobre o tema desse parágrafo ocorre no seguinte exem-
plo (simplificado): imagine que tenhamos a classe ContaDeAções em uma
aplicação referente à corretagem de valores. Uma de suas operações é
venderPosiçãoDeAções (ações, quantiaParaVenda, out vendaOK), que vende
uma dada quantia da posição de um cliente em uma dada ação. Outra
operação é venderTodasPosiçõesDeAções (out vendaOK), que vende todas as
posições de um cliente em ações (nessa conta). Esta última operação é su-
pérflua, porque a primeira operação atingiria o mesmo resultado se você
efetuasse um loop em torno de cada ação na conta, colocasse em quantia-

2. Em um ensaio sobre esse tema, um colega sugeriu que fosse acrescentada ainda outra opera-
ção replicada — virarNoSentidoAnti-Horário — com o pretexto de que invocar virarNoSenti-
doHorário com um ângulo negativo era artificial. O que você acha?
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 365

ParaVenda a quantia total que o cliente detivesse em cada ação e, em se-


guida, enviasse a mensagem venderPosiçãoDeAções (ações, quantiaPara-
Venda, out vendaOK).
Contudo, esse código cheio de voltas (loops), que não é assim tão sim-
ples, deverá ser escrito sempre que a aplicação precisar vender todas as
posições de ações. Isso poderia ocorrer em dezenas de lugares. Para evitar
esse esforço redobrado e a congeneridade de algoritmo acompanhante, o
desenhista de ContaDeAções criou venderTodasPosiçõesDeAções, que é uma
operação legítima; se bem que supérflua.3
Incidentalmente, a operação venderTodasPosiçõesDeAções poderia ser
desenhada como uma função ímpar fora do domínio de ContaDeAções.
Mas cabe a essa operação manter as contas de ações, implicando que o
ato de separar essa operação de sua classe introduziria mais congeneri-
dade no desenho global da aplicação.
Como você pôde deduzir desses exemplos, o comportamento replicado
faz surgir muitos argumentos sobre o desenho de classes. O comporta-
mento replicado pode fazer com que a interface de uma classe seja mais
complexa e mais difícil de ser entendida. Entretanto, como vimos, um de-
senhista pode optar por adiar a (ou até mesmo abster-se da) remoção do
comportamento replicado de uma interface devido à utilização existente.
De fato, um desenhista pode até mesmo introduzir comportamento repli-
cado a fim de prover uma operação especializada mais útil do que as ope-
rações gerais já existentes na interface.
Embora raramente haja uma resposta precisa para a questão se o
comportamento replicado é realmente solicitado, os desenhistas de clas-
ses devem conscientemente abordar esse tema sempre que eles agrega-
rem uma operação a uma classe. Do contrário, um comportamento
replicado barroco e injustificado se desenvolverá dentro da interface de
uma classe devido ao acréscimo por justaposição, ou proveniente dos ca-
prichos de um desenhista controvertido com personalidade forçada, ou de
demandas arbitrárias e muito pouco refletidas de usuários da classe.
Uma classe desse tipo será mais difícil de ser compreendida e modificada
do que realmente seria necessário.
7. Comportamento ideal
Uma interface de uma classe suporta comportamento ideal se ela impu-
ser as três propriedades seguintes:

3. Se você considerar venderTodasAsPosiçõesDeAções dramática demais para uma operação


ser útil, poderá aplicar o mesmo argumento utilizando a operação venderPosiçãoTotalDeA-
ções, que simplesmente vende todas as ações detidas por uma única companhia.
366 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• Um objeto em um estado válido pode mover-se somente para outro es-


tado válido.
• Um objeto pode mover-se para outro estado somente por meio de um
modo válido — ou seja, por um modo que seja parte do comportamen-
to prescrito para a classe do objeto.
• Há apenas um modo de utilização da interface a fim de efetivar uma
etapa de comportamento.

De novo, conhecer qual comportamento é válido e qual é ilegal depende


de se ter um bom entendimento da finalidade da classe e de se ter uma espe-
cificação completa de seu comportamento requerido; no sentido que retratei
previamente.
Um exemplo de uma interface com comportamento ideal é provido pela
classe PilhaAparelho, que é uma classe cujos objetos são pilhas de aparelhos.4
Sua interface contém estas operações:

topo: Aparelho; // retorna o elemento do topo da pilha


extrair; // remove o elemento do topo da pilha
empurrar (novoElemento: Aparelho); // coloca um novo elemento no topo da
// pilha
estáVazia: Booleano; // retorna se a pilha está vazia
estáCheia: Booleano; // retorna se a pilha está cheia

A interface acima é ideal porque suas cinco operações abrangem toda a


faixa de comportamento de uma pilha normal, e há somente um modo de se
pôr em prática qualquer operação particular em uma pilha. (Repare, entretan-
to, que a operação Pilha.extrair normalmente também retorna o elemento do
topo da pilha. Essa definição de extrair acrescentaria um pequeno comporta-
mento replicado para a interface da classe Pilha.)

14.3 Coesão de Operações em uma Interface de Classe


O terceiro modo de arrumar a interface de sua classe (depois de atingir com-
portamentos e estados ideais) é fortalecer a coesão das operações individuais.
Durante décadas no desenho estruturado (SD — Structured Design), a
coesão de módulos tem sido um critério padrão para estimar quantitativamen-
te a qualidade de um módulo de procedimento. Em orientação a objeto, as ope-

4. Isso é derivado da classe genérica Pilha, na qual o parâmetro de classe formal C estava fadado
a ser o parâmetro de classe efetivo Aparelho neste exemplo.
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 367

rações deverão ter uma boa coesão (da mesma forma que os módulos de pro-
cedimento individuais em SD).5
Em SD, a coesão realça o propósito do desenhista em criar um módulo
particular — quer ele tenha visto uma forte razão baseada na aplicação para
o módulo, quer ele tenha simplesmente apinhado algumas linhas aleatórias de
código em um procedimento. Por exemplo, determinarPontoParaNovoPedidoE-
mInventário muito provavelmente tem uma alta coesão, ao passo que façaAlgo-
Miscelânea provavelmente tem uma baixa coesão.
Uma pobre coesão de operações resulta da combinação mal orientada de
operações que deveriam ter sido mantidas separadas. As duas versões desse
tipo de combinação mal gerada provocam, respectivamente, coesão alternada
e coesão múltipla, ambas descritas a seguir. Eu então concluo esta seção com
uma observação mais positiva, descrevendo coesão funcional, ou ideal: aquela
que é atingida ao se manterem operações distintas separadas de maneira ha-
bilidosa.
1. Coesão alternada
A coesão alternada aparece quando um desenhista combina diversas eta-
pas de comportamento em uma única operação, que, no recebimento de
uma mensagem, aplica somente uma etapa de comportamento do objeto.6
Em outras palavras, alguém enviando uma mensagem para invocar a
operação deverá prover um sinalizador (ou um switch) que informe à ope-
ração qual etapa de comportamento deverá ser executada desta vez.
Por exemplo, Retângulo poderia ter uma operação

reduzirProporcionalmenteOuGirar (fatorDeEscala: NúmeroReal,


ânguloDeRotação: Ângulo, oQueFazer: Booleano)

reduzirProporcionalmenteOuGirar altera a dimensão de um retângulo ou o


gira, dependendo do que é atribuído a oQueFazer, como true ou false. Ob-
serve que em cada caso só um dos primeiros argumentos é utilizado; o ou-
tro é um simulacro inútil. Contando com um pouco mais de
esquematização, entretanto, um desenhista pervertido poderia fazer com
que a interface da operação ficasse ainda pior — como esta:

5. Em desenho estruturado, Larry Constantine classificou os sete níveis possíveis de coesão de


módulo em ordem aproximada de qualidade de desenho, dos piores aos melhores: funcional,
seqüencial, comunicativo, procedural, temporal, lógico e coincidente. Veja, por exemplo, Your-
don e Constantine, 1979, ou Page-Jones, 1988.
6. A coesão alternada é equivalente à coesão lógica de desenho estruturado (SD). Ela poderia
também conter a coesão coincidente de SD, se parte do código de uma operação com coesão
alternada fosse completamente irrelevante para o objeto no qual a operação está executando.
Mais uma vez, só um dedicado Genghis cometeria um crime dessa natureza.
368 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

reduzirProporcionalmenteOuGirar (proporção: NúmeroReal,


oQueFazer: Booleano)

Nesse caso, o argumento proporção está atrapalhadamente desempe-


nhando uma tarefa dupla: ele significa um fatorDeEscala em um caso, mas
um ânguloDeRotação no outro.
Esse era um exemplo moderado. Estou certo de que você pode visua-
lizar uma operação grotesca com 23 etapas de comportamento abarrota-
das no mesmo e 15 argumentos necessários em cada mensagem, uma
dúzia dos quais são normalmente simulacros. Essa espécie de desenho de
operação zomba da orientação a objeto. Vamos colocar uma mortalha
nele, e prosseguir na procura de uma coesão mais agradável e gentil.
2. Coesão múltipla
A coesão múltipla é similar à coesão alternada, na qual um desenhista
amontoou diversas etapas de comportamento em uma única operação.
Entretanto, quando uma operação com coesão múltipla executa, ela apli-
ca todas as (em vez de uma só) etapas de comportamento para o objeto.7
Por exemplo, uma operação Pessoa.alterarEndereçoENúmeroDeTelefone,
que altera tanto o endereço como o número de telefone de uma pessoa,
tem coesão múltipla.
Muitas vezes, uma operação desse tipo produz uma interface de classe
com comportamento incompleto (ou seja, interface que carece de algum
comportamento válido) porque a operação põe em prática diversas etapas,
e portanto pode pular alguns estados válidos. No SD, ou desenho estru-
turado, isso não constituía um grande problema, pelo fato de que tal mó-
dulo sempre poderia ser decomposto em dois módulos, alterarEndereço e
alterarNúmeroDeTelefone, cada um deles podendo ser solicitado indivi-
dualmente. Em desenho orientado a objeto, você pode fazer a mesma coi-
sa contanto que seja assegurado às duas operações serem publicamente
disponíveis na interface. (Mas então você com razão poderia estar se per-
guntando: se as operações são separadamente disponíveis, por que reter
o comportamento replicado que as operações combinadas, alterarEndere-
çoENúmeroDeTelefone, introduziram? A única razão é que outro código

7. A coesão múltipla inclui a coesão seqüencial e a coesão comunicativa de desenho estruturado


(SD). A coesão procedural e temporal de uma operação é rara, visto que todo o código em uma
operação está trabalhando sobre o mesmo objeto. Este fato estimula seu nível de coesão. En-
tretanto, suponho que Genghis poderia criar uma operação proceduralmente coesiva, em que
parte do código seria irrelevante para o objeto sobre o qual a operação estivesse executando.
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 369

talvez estivesse utilizando-o, mas ele provavelmente seria lançado fora


assim que possível.)
Em uma classe Pessoa que vi recentemente, o desenhista não tinha de-
composto essa operação em duas. Ele gabou-se para mim dizendo que não
precisava decompô-la, porque sua operação alterarEndereçoENúmeroDeTe-
lefone “seria capaz de fazer qualquer coisa” e suportava todos os compor-
tamentos válidos. Ele, posteriormente, afirmou que: “Se uma pessoa
alterar o endereço mas não o número de telefone, então você simplesmen-
te invocará a operação com um número de telefone nulo, e a operação é
inteligente o bastante para evitar que se altere númeroDeTelefone”. Em-
bora isso possa ser correto, este desenho cria uma interface embaraçosa
por meio da qual algumas mensagens necessitam transmitir argumentos
simulacros. Isso é, na verdade, uma coesão alternada tenuemente velada,
na qual o valor nulo está servindo como um sinalizador um tanto oculto.
Ocorre outra forma de coesão múltipla quando um desenhista insere
em uma operação alguma função que realiza cálculos extrínsecos que de-
veriam ser realizados fora do domínio da classe. Por exemplo, digamos
que área seja uma operação obter de Retângulo. Agora, vamos introduzir
outra operação, áreaMaiorQue (algumaÁrea: Área): Booleano, que nos infor-
ma se o retângulo é maior do que alguma outra área. Ela seria utilizada
da seguinte forma:

nossoRetânguloÉMaior: Booleano := retângulo.áreaMaiorQue


(algumaÁrea);
if nossoRetânguloÉMaior
then...

A operação áreaMaiorQue, portanto, realiza duas funções. A primeira de-


termina a área do retângulo; a segunda compara essa área com alguma
quantia especificada que foi passada a ela sob a forma de argumento de
entrada de dados. Entretanto, a determinação de qual área é maior de-
verá ser feita pelo objeto que envia a mensagem, e não pelo objeto Retân-
gulo. O código no objeto remetente simplesmente se pareceria com:

if retângulo.área > algumaÁrea


then...

A razão de se deslocar a comparação para fora da classe Retângulo é que


ela não faz parte da abstração de Retângulo. E dedicar uma operação in-
teira a cada cálculo extrínseco, que porventura ocorra fora de uma dada
370 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

classe, chega a ser algo extravagante. Entretanto, possivelmente você


percebeu uma semelhança entre a operação áreaMaiorQue e a operação
venderTodasPosiçõesDeAções da seção 14.2, que sugeri que poderia perma-
necer como parte da interface daquela classe.
A questão é esta: certifique-se de que sua classe provê operações que
não se envolvam em atividades extracurriculares. Isso é mais fácil de di-
zer do que fazer — afinal de contas, o que “extracurricular” realmente
significa para uma dada classe? Embora a resposta sempre seja um con-
vite ao raciocínio, se você tiver dúvidas sobre uma operação, então ela
provavelmente não faz parte da classe. Entretanto, talvez você queira ve-
rificar se a inclusão dessa operação vai aumentar ou diminuir a congene-
ridade.
Finalmente, se o seu critério para incluir uma operação gerará uma
proliferação de operações, então sua operação provavelmente não deverá
pertencer à classe. Por exemplo, se você habilitar a operação áreaMaior-
Que, então, por que não habilitar igualmente áreaMenorQue e áreaIgualA?
3. Coesão funcional
Coesão funcional é um termo retirado diretamente do desenho estrutura-
do, no qual ele representa o nível ideal de coesão para um módulo. Uma
operação funcionalmente coesiva é uma operação dedicada a uma única
etapa de comportamento, conforme definido pelas necessidades do proble-
ma. A coesão funcional é também conhecida como coesão ideal.
O nome de uma operação fornece o indício para sua coesão: um nome
e implica coesão múltipla, enquanto um nome ou implica coesão alterna-
da. Entretanto, um nome expressivo, não contando com e nem com ou,
implica uma operação com coesão funcional.
Por exemplo, Tanque.encher, Retângulo.área, ItemDeProduto.peso, Pedi-
doDeCliente.despachar, Cliente.fixarLimiteDeCrédito, Aeroplano.virar, Con-
ta.fazerDepósito e Cliente.númeroDeTelefone constituiriam operações
funcionalmente coesivas. Cada operação executa uma única etapa de
comportamento apropriado para sua classe; cada operação, igualmente,
tem um nome forte. De maneira inversa, você sabe que não é muito bom
ter uma operação com um nome inexpressivo, como Cliente.fazerAlguma-
Coisa.
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 371

14.4 Resumo
Este capítulo abordou a qualidade da interface externa de uma classe em ter-
mos de quão bem as suas operações suportam o espaço-estado, o comporta-
mento e a coesão dessa classe.
A interface de uma classe pode suportar o espaço-estado desta de acordo
com quatro modos: estados ilegais, em que a interface permite a um objeto
atingir estados que são ilegais para sua classe (ou seja, estados que violam a
invariante de classe); estados incompletos, em que a interface não permite a
um objeto atingir alguns estados que são válidos para sua classe; estados ina-
propriados, em que a interface manifesta que alguns estados não são adequa-
dos para a abstração da classe; e estados ideais, em que a interface permite a
um objeto atingir todos os estados que são válidos para sua classe, e somente
os estados que são válidos para sua classe. Todo desenhista de classes deve vi-
sar uma interface de classe que suporte estados ideais.
A interface de uma classe pode suportar o comportamento desta sob sete
modos: comportamento ilegal, em que a interface permite a um objeto execu-
tar transições de estado que são ilegais para sua classe; comportamento peri-
goso, em que a interface de uma classe pede que um objeto execute algumas
transições de estado via diversas mensagens que conduzem o objeto por esta-
dos intermediários (porém ilegais); comportamento irrelevante, em que a inter-
face de uma classe suporta comportamento extrínseco à classe;
comportamento incompleto, em que a interface não permite a um objeto exe-
cutar algumas transições de estado que são válidas para sua classe; compor-
tamento inábil, em que a interface pede que um objeto execute algumas
transições de estado via diversas mensagens que conduzem o objeto por meio
de estados intermediários (porém válidos); comportamento replicado, em que
a interface suporta o mesmo comportamento segundo diversos modos; e com-
portamento ideal, em que a interface permite a um objeto executar, segundo
um único modo, transições de estado que são válidas para sua classe. Todo de-
senhista de classes deve visar uma interface de classe que suporte comporta-
mento ideal.
Uma operação particular tem três coesões possíveis: coesão alternada, em
que um desenhista combina em uma operação diversas etapas de comporta-
mento a serem executadas alternativamente, dependendo do valor de um si-
nalizador; coesão múltipla, em que um desenhista combina em uma operação
diversas etapas de comportamento a serem executadas em conjunto; e coesão
funcional (ou ideal), em que um desenhista cria uma operação dedicada a pôr
em prática uma única etapa de comportamento. A força e a clareza do nome
372 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

de uma operação freqüentemente revela sua provável coesão. Todo desenhista


de classes deve criar operações com coesão funcional.
A coesão de operações é nosso critério final para a qualidade de uma clas-
se. A classe ideal, portanto, tem estas propriedades: ela apresenta coesão de
classe ideal; sua interface suporta estados e comportamentos ideais; cada uma
de suas operações é funcionalmente (idealmente) coesiva; a classe não tem um
grau de dependência desnecessário, possui um grau de dependência apropria-
do para seu domínio; ela não tem uma congeneridade desnecessária, e não
possui qualquer congeneridade que atravesse fronteiras para atingir outras
classes. Além do mais, ela obedece aos princípios da conformidade de tipo e do
comportamento fechado.
Uma classe com essas qualidades exemplares de desenho é uma imple-
mentação valiosa de um tipo de dado abstrato. Ela será tão robusta, confiável,
extensível, reutilizável e poderá ser mantida como uma classe jamais pode ser.
Todos os que olharem para isso chorarão de felicidade! Mais importante ainda,
se construir seus sistemas orientados a objeto a partir de classes bem dese-
nhadas, você obterá o máximo valor que a orientação a objeto é capaz de pro-
porcionar à sua organização.

14.5 Exercícios
1. Na seção 14.2, na qual tratei de interfaces com comportamento replicado,
sugeri que qualquer desenhista vez por outra poderia deliberadamente
introduzir comportamento replicado em uma interface de classe, acres-
centando operações especializadas para utilidade dos usuários. Como
você poderia utilizar anéis de operações, os quais vimos no Capítulo 13,
para essa finalidade? De que forma a idéia de sobreposição, discutida na
seção 1.8, também poderia auxiliar?
2. Muitos princípios de desenho estruturado ainda se aplicam a desenho
orientado a objeto. Escolha um princípio de desenho estruturado e, de
maneira concisa, explique como ele poderia ser conformado ao desenho
orientado a objeto.
3. Suponha uma aplicação referente à expedição na qual os usuários reme-
tem engradados de pacotes aos clientes. A Figura 14.1 mostra a estrutura
de agregação de um objeto da classe UnidadeDeRemessa. A estrutura com-
preende um conjunto de pacotes (o conteúdo propriamente dito da remes-
sa) e um engradado-recipiente contendo esses pacotes.
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 373

Figura 14.1 Uma unidade de remessa é uma agregação de um engradado-


recipiente com um conjunto de pacotes-conteúdo.

O peso total desse conjunto é a soma do peso do engradado-recipiente com


os pesos de todos os pacotes-conteúdo. A Figura 14.2 mostra um possível
desenho para UnidadeDeRemessa.peso.

Figura 14.2 Uma unidade de remessa descobre seu


peso interrogando os constituintes dela.

No desenho mostrado na Figura 14.2, o método implementador da opera-


ção UnidadeDeRemessa.peso envia uma mensagem interrogativa (ou seja,
uma mensagem obter, conforme mencionado no Capítulo 1) primeiro para
o engradado-recipiente obter seu peso e, a seguir, e de forma interativa,
para que todos os pacotes-conteúdo obtenham seus pesos. O método soma
cada um desses pesos a um total corrente, o qual ele retorna como o peso
total da unidade de remessa.
374 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Será que é possível desenhar um método para UnidadeDeRemessa.peso de


forma que os objetos constituintes tomem a iniciativa de enviar informa-
ções ao objeto agregado? De que forma isso mudaria a natureza da inter-
face entre UnidadeDeRemessa e, digamos, PacoteDeRemessa? Esse tipo de
desenho apresentaria vantagens ou desvantagens?
4. Suponha que tivéssemos uma aplicação para rastreamento de cachorros
que tem, como um de seus requisitos, a necessidade de registrar qual pes-
soa é dona de quais cachorros. (Vimos este exemplo na seção 9.3.3, e tal-
vez você deseje rever alguns dos tópicos que levantamos lá.) Digamos
que, de maneira idêntica, também precisamos saber, a respeito de uma
dada pessoa, quantos cachorros são possuídos por ela.
As Figuras 14.3 a 14.6 mostram quatro possíveis desenhos. Utilizando os
princípios de desenho e terminologia com os quais você travou contato
neste livro, comente sobre os prós e contras de cada abordagem de dese-
nho. Você provavelmente achará proveitoso ler os seguintes resumos con-
cisos de cada desenho antes de iniciar a tarefa.
Desenho A (veja a Figura 14.3): O desenhista criou duas classes, Pessoa
e Cachorro, e a seta de navegabilidade sugere que a classe Pessoa mantém
o vínculo entre uma pessoa e seus cachorros, talvez com uma variável ca-
chorrosPossuídos: Conjunto<Cachorro>. Um atributo apenas para leitura
de Pessoa é denominado númeroDeCachorrosPossuídos.

Figura 14.3 Desenho A para “pessoa possui cachorro”.

Desenho B (veja a Figura 14.4): O desenhista criou três classes. A classe


do meio, PosseEntrePessoaECachorro, é dedicada a manter o relacionamen-
to entre Pessoa e Cachorro. Cada instância de PosseEntrePessoaECachorro
associa uma pessoa que tem um cachorro com um conjunto (não vazio) de
cachorros. A operação obter númeroDeCachorrosPossuídos é uma operação
de classe de PosseEntrePessoaECachorro, para a qual você passa um objeto
Pessoa como um argumento. A operação encontra a instância apropriada
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 375

de PosseEntrePessoaECachorro para essa pessoa (se houver), e então retor-


na o tamanho do “conjunto de cachorros” para essa instância.

Figura 14.4 Desenho B para “pessoa possui cachorro”.

Desenho C (veja a Figura 14.5): O desenhista criou uma classe de asso-


ciação abstrata, DonoDeCachorro, que mantém o vínculo com cachorros e
retorna NúmeroDeCachorrosPossuídos, como Cachorro no desenho A. A
classe PessoaPossuindoCachorro, que é criada por herança múltipla de Pes-
soa e DonoDeCachorro, é a classe a partir da qual você gera objetos que
representam donos de cachorros.

Figura 14.5 Desenho C para “pessoa possui cachorro”.

Desenho D (veja a Figura 14.6): O desenhista criou uma classe Dono, que,
ao contrário de PessoaPossuindoCachorro no desenho C, adquire suas pro-
priedades não por meio de herança mas por referir-se a dois objetos, um
da classe Pessoa e outro da classe DonoDeCachorro. (Note que a última é
uma classe concreta, em contraste a seu complemento no desenho C.)
Esse desenho assume a função de “dividir” uma única coisa do mundo
real (uma pessoa possuindo um cachorro) em vários aspectos, função essa
376 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

que abordei no exercício 2 do Capítulo 12. Incidentalmente, visto que nem


sempre está presente uma referência para o objeto DonoDeCachorro — ob-
serve a multiplicidade de 0..1 —, o desenhista agora talvez devesse no-
mear Dono como DonoPotencial. (O atributo éDonoDeCachorro é imputado
como true se um dono potencial efetivamente é um dono de cachorro.)

Figura 14.6 Desenho D para “pessoa possui cachorro”.

14.5 Respostas
1. Um desenhista poderia posicionar as operações mais especializadas no
anel externo e as operações mais gerais no anel interno. As operações ex-
ternas, em seguida, seriam implementadas por meio de mensagens para
que utilizassem as operações internas.
A sobreposição capacita operações diferentes na mesma interface a te-
rem o mesmo nome. Isso pode ser útil para a implementação de compor-
tamento replicado nos casos em que uma operação geral na interface tem
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 377

mais argumentos do que uma operação especializada similar. Entretanto,


muitas vezes nessa situação é melhor utilizar nomes distintos para as
operações. Por exemplo, o nome venderTodasAsAções é mais significativo
(e seguro) do que um mero nome vender, isento de argumentos.
Eu também tenho construído ocasionalmente uma classe auxiliar, do
tipo “operação de anel”, e separada de uma “classe central”, para prover
operações úteis, mas muito pouco necessárias, relacionadas com as ope-
rações sobre a classe central. Por exemplo, para solucionar a proliferação
de operações, tais como áreaMaiorQue (que poderia ocasionar coesão múl-
tipla, conforme vimos na seção 14.3), você poderia desenhar a classe Uti-
lidadesDeRetângulo (talvez como um pacote de utilidades), que teria
operações como áreaMaiorQue, áreaMenorQue e assim por diante. Muito
embora UtilidadesDeRetângulo, por exemplo, talvez necessite dessas ope-
rações, a classe central Retângulo permaneceria livre dessas operações
marginais, a inclusão das quais possivelmente faria de Retângulo uma
classe mais difícil de ser entendida pelos usuários comuns. Em resumo,
essa abordagem de desenho implica um conjunto de operações que poderá
compreender um “anel externo” de uma classe e o posiciona em uma clas-
se separada.
2. Um exemplo de um critério de desenho estruturado é o do fan-out. Um
excessivo fan-out, proveniente de um módulo de SD (desenho estrutura-
do), muitas vezes sugere um nível de ausências na hierarquia do diagra-
ma de estruturas. De maneira similar, em desenho orientado a objeto, um
excessivo fan-out (digamos, sete ou mais) em uma hierarquia de super-
classe/subclasse poderá implicar classes, ou até mesmo níveis inteiros,
ausentes de estrutura. Igualmente, se você encontrar muitas mensagens
provenientes de um método, talvez queira decompor uma função ou um
procedimento para reduzir a complexidade do método. Esse elemento de-
composto também poderá ser útil para métodos implementadores de ou-
tras operações dessa classe, ou até mesmo para métodos de operações de
outras classes.
3. O desenho que mostrei é baseado na “extração de informações dos obje-
tos”, caso isso seja necessário. Embora esse desenho seja claro e fácil de
ser entendido, possivelmente ele não executará muito rapidamente quan-
do você precisar do peso de uma unidade de remessa. Uma mensagem
para UnidadeDeRemessa.peso poderá gerar centenas de mensagens para
os objetos que representam pacotes. Para aperfeiçoar o tempo de execu-
ção de peso, poderíamos modificar o desenho na Figura 14.2 de forma que
378 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

a classe UnidadeDeRemessa tenha uma variável (digamos, pesoTotal) que


sempre detém o peso atual da unidade de remessa. Assim, UnidadeDeRe-
messa.peso iria simplesmente retornar o valor dessa variável. Esse méto-
do seria executado em um piscar de olhos.
Entretanto, para fazermos isso, devemos assegurar que os objetos
constituintes continuem a manter pesoTotal atualizado: sempre que um
objeto constituinte for acrescentado, ou mudar seu peso, ele deverá en-
viar uma mensagem para o objeto unidade de remessa, informando-o da
modificação. Isso constituira uma mensagem informativa, mensagem
essa que “empurra informações” para os objetos que necessitem da mesma.
Esse segundo desenho, embora provavelmente mais eficiente, é igual-
mente mais complexo. Agora, todos os objetos constituintes necessitam se
precaver do objeto agregado, o qual poderia prejudicar suas reutilizações
(porque eles ficam sobrecarregados com a maquinaria de outra classe). O
objeto agregado igualmente necessita de pelo menos uma outra operação
para capturar as informações passadas na mensagem informativa. Final-
mente, se ocorrerem freqüentes mudanças nos pesos dos pacotes, e as
mensagens invocando UnidadeDeRemessa.peso forem infreqüentes, em de-
corrência poderá haver pouca ou nenhuma melhora quanto à eficiência
em run-time global.
Portanto, a menos que tenha fortes razões em contrário, você deveria
desenhar objetos agregados para obter suas informações dos constituin-
tes via mensagens interrogativas, “pull”, em vez de via mensagens infor-
mativas, “push”, emanando dos constituintes.8
4. Veja aqui meus comentários sobre os quatro desenhos.
Desenho A: Este desenho apresenta o mérito de ser extremamente sim-
ples. Ele tem o menor número de classes entre os quatro desenhos, e é
muito fácil de ser utilizado. Na verdade, refiro-me ao fato de que, se você
tiver um objeto da classe Pessoa, digamos fred, conseqüentemente, para
descobrir quantos cachorros Fred tem, você simplesmente escreverá:

CachorrosPossuídosPorFred := fred.númeroDeCachorrosPossuídos
Entretanto, a maior desvantagem desse desenho reside no fato de a
classe Pessoa apresentar grau de dependência com a classe Cachorro.

8. Incidentalmente, esse par de desenhos ilustra a diferença entre um atributo e uma variável.
Ambos os desenhos implementam o atributo peso. Entretanto, o primeiro desenho não tem
qualquer variável correspondente a peso, e o valor do peso é calculado por sua operação obter
“às carreiras”. O segundo desenho tem uma variável explícita correspondente a peso, denomi-
nada pesoTotal nesse exemplo.
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 379

(Como vimos na seção 9.3.3, isso gera coesão de papel misto em Pessoa.)
Em termos práticos, significa que Pessoa necessita de muitas “operações
envolvendo cachorros” em sua interface para manipular, por exemplo, a
aquisição de cachorros e cachorros perdidos. Suponha que você queira co-
locar Pessoa em nossa biblioteca de classes e, mais tarde, a reutilize em
uma aplicação pessoal. Os reutilizadores de Pessoa ficariam muito surpre-
sos ao encontrar todas as referências de Cachorro em Pessoa. Na verdade,
poderia até mesmo existir um problema ao tomar-se a classe Pessoa para
compilar ou vincular em uma aplicação que nem disponha de Cachorro!
Eu, portanto, recomendaria o desenho A somente em situações em que
você não teria qualquer intenção de reutilizar a classe Pessoa em outras
aplicações.
Desenho B: O desenho B soluciona o problema de coesão de papel mis-
to de Pessoa existente no desenho A, criando uma classe (PosseEntrePes-
soaECachorro) para vincular uma pessoa e os cachorros dessa pessoa.
Cada objeto dessa classe vincula um objeto Pessoa com um objeto da clas-
se Conjunto<Cachorro>. Está perfeito para PosseEntrePessoaECachorro ser
sobrecarregada com Pessoa e Cachorro, visto que a “condição de pessoa” e
a “condição de cachorro” são intrínsecas à noção da posse de cachorros.
De maneira idêntica, e falando-se intuitivamente, númeroDeCachorrosPos-
suídos é melhor situado como um atributo do relacionamento entre pes-
soas e cachorros do que como um atributo de pessoa.
Entretanto, sempre que mostro o desenho B para qualquer programa-
dor orientado a objeto típico, a reação dele é: “Este desenho é esquisito!”
A razão para essa reação é que, para se encontrar o número requerido de
cachorros, nós não mais simplesmente invocamos uma operação de ins-
tância sobre um objeto escrevendo fred.númeroDeCachorrosPossuídos. Em
seu lugar, devemos invocar uma operação de classe sobre uma classe, es-
crevendo PosseEntrePessoaECachorro.Núm.DeCachorrosPossuídos (fred). O
método para essa operação perscruta minuciosamente uma tabela (uma
variável de classe dentro de PosseEntrePessoaECachorro, que detém iden-
tificadores para todos os objetos da classe) para encontrar o objeto refe-
renciado por fred e o número de cachorros no conjunto associado. Para
programadores acostumados somente com operações de instância, essa
abordagem pode parecer artificial.
Outra objeção origina-se da palavra perscruta no parágrafo anterior.
Qualquer programador poderia exclamar um tanto aflito: “Você quer di-
zer que o método terá de examinar cuidadosamente uma tabela inteira?
380 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Puxa, isso é contraproducente!” Essa objeção talvez seja válida; ela de-
pende de quão habilmente você desenhou as partes intrínsecas de Pos-
seEntrePessoaECachorro. (Note que você talvez queira tentar desenhar
essa classe para minimizar esses problemas de eficiência.)
O desenho B promove reutilização ao remover os graus de dependência
desnecessários da classe Pessoa. Entretanto, poderá sofrer em termos de efi-
ciência. Também poderá sofrer de uma sintaxe de mensagem embaraçosa
(ao menos algumas pessoas consideram assim): a sintaxe de mensagens de
classes, preferentemente à sintaxe de mensagens de instâncias.
(Um exercício resultante: a classe PosseEntre PessoaECachorro poderia
ser generalizada de forma a suportar relacionamentos binários, inclusive
relacionamentos de “muitos-para-muitos”, em lugar dos relacionamentos
entre pessoas e cachorros? Em caso afirmativo, o conceito de generalidade
— isto é, de classes parametrizadas — comprovaria sua utilidade?)
DesenhoC: Este desenho, que utiliza a classe de associação DonoDeCa-
chorro para implementar a maquinaria para a posse de cachorros, apre-
senta o mérito de ser flexível. Por exemplo, poderíamos facilmente criar
a classe EmpresaDePosseDeCachorros, com essa classe sendo criada via he-
rança a partir de DonoDeCachorro e Empresa. Esse desenho também nos
habilita a enviar uma mensagem diretamente para fred (agora, da classe
PessoaPossuindoCachorro) para descobrir quantos cachorros possui essa
pessoa. (Observe que o objeto fred executará a operação obter númeroDe-
CachorrosPossuídos via herança de DonoDeCachorro). Outra vantagem
desse desenho é que, uma vez que nem Pessoa nem Cachorro tem coesão
de papel misto, muito provavelmente as classes serão de fácil reutilização.
Mas o que aconteceria se Fred comprasse um barco, um carro e um
gato? Para lidar com a posse de cachorros, gatos, barcos e carros segundo
esse desenho, talvez tivéssemos de criar até 15 classes! (Exemplos in-
cluem PessoaPossuindoCachorroEGato e PessoaPossuindoCachorroECarroE-
Barco.)9

9. O problema básico aqui, em minha opinião, é que todas as linguagens orientadas a objeto de
tendência dominante atuais contêm um defeito fundamental: elas não suportam a habilidade
de um objeto de adquirir ou perder associação de classe ou de deter diversas associações de
classe (salvo o que é dado a entender pela hierarquia de classe) de cada vez. Uma abordagem
de desenho capaz de prover essas habilidades é um work-around para um defeito de lingua-
gem orientada a objeto. Espero que as linguagens orientadas a objeto de tendência dominante
brevemente possuam recursos de migração de classes, como algumas linguagens de banco de
dados orientadas a objeto já possuem. (A linguagem Iris apresenta esse recurso.) Entretanto,
admito que a migração geral de classes não é um tema fácil. Para acompanhar esse tópico de
pesquisa, veja, por exemplo, Bertino e Martino, 1993, e Zdonik, 1990.
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 381

Igualmente, quando geramos o objeto conhecido como fred, temos de


fazê-lo a partir da classe PessoaPossuindoCachorro. Isso implica que sabe-
mos que (no mundo real) Fred é um dono de cachorro e que é improvável
que ele abandone esse papel. Entretanto, aqui reencontramos o assunto
do particionamento dinâmico, que deparamos, pela primeira vez, na seção
4.1.3: Fred pode fazer parte da companhia venerável de donos de cachor-
ro neste exato momento, mas no próximo ano ele poderá tornar-se um
“ex-dono de cachorro”.
Portanto, se Fred, no futuro, deixar de ser um dono de cachorro, então
deveremos eliminar o objeto fred para facilitar a migração de classes que
o particionamento dinâmico demanda, certificando-nos primeiramente de
que copiamos as informações de “pessoa” contidas em fred. A seguir, de-
veremos gerar de novo fred como um objeto simplesmente da classe Pes-
soa, utilizando a informação copiada para inicializar o objeto.10 A
desvantagem disso é certamente é que o novo “objeto fred” terá um iden-
tificador diferente em relação ao identificador antigo, o que poderá tor-
nar-se um problema grave se houver referências existentes para o objeto
antigo por todo o sistema.
Conseqüentemente, o desenho C obtém os melhores resultados em si-
tuações nas quais o particionamento é estático e é improvável existirem
explosões combinatórias de classes, como no exemplo anterior. (Um exer-
cício resultante: você talvez queira experimentar uma variação no dese-
nho C que agrupe cachorros, gatos, barcos e carros sob o termo mais geral
“posse”, e que previna a imperfeição explosiva anterior.)
Desenho D: A característica distintiva desse desenho é que, no caso de
determinado dono de cachorro do mundo real, como Fred, temos de gerar
três objetos no sistema:

1. um objeto da classe Pessoa para reter o aspecto das informações pessoais


de Fred (tais como os atributos nome e endereço);
2. um objeto da classe DonoDeCachorro para reter o aspecto de possuidor de
cachorro de Fred (tal como o atributo númeroDeCachorrosPossuídos);
3. um objeto da classe composta Dono, que vai se referir aos primeiros dois
objetos como componentes e, em seguida, agrupar todo “o material refe-
rente a Fred” em conjunto. As operações obter sobre a classe Dono (con-
forme mostrado na Figura 14.6) recebem seus valores via mensagens de

10. Nós igualmente encontramos esse problema de migração no caso de uma espécie animal mo-
vendo-se para dentro e para fora do risco de ser extinta. (Veja exercício 1 do Capítulo 12.)
382 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

difusão para os atributos similarmente nomeados nos objetos componen-


tes apropriados.

O desenho D tem uma vantagem sobre o desenho C pelo seu tratamento


de desenho de particionamento dinâmico. Escrevendo isso, estou querendo di-
zer que, caso Fred deixe de ser um dono de cachorro, então podemos simples-
mente eliminar o objeto da classe DonoDeCachorro, enquanto deixamos intacto
o objeto da classe Dono. Além disso, podemos introduzir gatos, barcos, carros
e assim por diante, sem uma explosão combinatória — apenas uma classe adi-
cional para cada tipo de posse — e habilitar Fred a se mover para dentro e
para fora desses papéis, com pouca dificuldade pertinente a desenho ou pro-
gramação.
O desenho D funciona bem sempre que uma coisa do mundo real apre-
senta diversos aspectos derivados de vários papéis, e move-se para fora e para
dentro desses papéis. Entretanto, essa abordagem de desenho tem o inconve-
niente de que uma única coisa no mundo real se torna vários objetos no sis-
tema. Além do mais, a comodidade da herança é substituída pelo tédio da
transmissão de mensagens.11
Observação: As quatro abordagens de desenho neste exercício são inde-
pendentes de pessoas e cachorros. Em outras palavras, elas seriam úteis para
“pessoa possui sapo” e “empresa possui barco” (bem como muitas outras asso-
ciações que não envolvem posse legal). Elas, portanto, provêm exemplos de
modelos, estabelecem etapas que podem ser postas em prática, aplicação após
aplicação, uma vez que não são dependentes da semântica específica de qual-
quer aplicação.
Portanto este exercício é típico de desenho orientado a objeto: um único
requisito de análise pode ser desenhado de muitos modos legítimos. Todos os
quatro desenhos anteriores são válidos, já que eles podem ser codificados, se-
rão executados e vão satisfazer os requisitos de análise. Assim, sua escolha de
qual desenho implantar depende de outros fatores que não o requisito em si
como um todo — fatores como simplicidade, flexibilidade, generalidade ou efi-
ciência.
Em desenho orientado a objeto, raramente uma única resposta certa se
torna conhecida. Em vez disso, na qualidade de desenhista, você deverá con-

11. Dividir um objeto em aspectos (como no desenho D) é uma excelente abordagem de desenho
para tratar de “subtipos migrantes”. Por exemplo, considere PedidoDeCliente uma instância
que migra de PedidoDeTentativa para PedidoAprovado, para PedidoEfetivado e assim por
diante. A classe composta PedidoDeCliente poderia se referir a quaisquer classe ou a todas
as classes componentes PedidoDeTentativa, PedidoAprovado e assim por diante. Veja Odell
e Martin, 1995, para mais detalhes sobre esse exemplo.
Cap. 14 COESÃO DE CLASSE, SUPORTE DE ESTADOS E DE COMPORTAMENTOS 383

siderar as vantagens e desvantagens de diversos desenhos possíveis. Este li-


vro apresentou alguns princípios de desenho por intermédio dos quais você
pode julgar, e termos com os quais você pode discutir, os méritos de uma abor-
dagem de desenho quando comparada à outra.
Somente você e sua equipe de desenho podem evitar a manutenção de fo-
cos de incêndio. Em última análise, você deverá decidir qual desenho é mais
apropriado para sua aplicação. Procure se divertir desenhando o seu próximo
sistema!
D esenhando um componente
de software
FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML
DESENHANDO UM COMPONENTE DE SOFTWARE
Desenhando um Componente de Software

E ste capítulo não trata dos objetos componentes simples do Capítulo 4, os


quais representam partes dos objetos compostos do mundo real, mas sim
dos componentes de software maiores; o próximo grande aperfeiçoamento de
nossa vida depois dos objetos comuns antigos. Como ocorre normalmente com
os termos existentes na indústria de software, existe muito debate sobre o que
exatamente significa componente. Portanto, a título de esclarecimento, a pri-
meira seção propõe uma lista das características que, a meu ver, abarcam as
propriedades cardinais dos componentes.
A segunda seção compara e contrasta as características dos componentes
com as do software orientado a objeto, explorando semelhanças entre os com-
ponentes e os objetos e/ou classes.
A terceira seção se aprofunda em um exemplo de um componente. Esse
exemplo não faz qualquer tentativa para aderir a qualquer um dos padrões
atuais rivais — e nem um pouco compatíveis — da tecnologia dos componen-
tes. Ele também não lida com temas tais como monitores de transação distri-
buída, que estão fora do escopo deste texto. Em vez disso, procurando manter
o espírito deste livro, tento ultrapassar o nível da tecnologia “volátil” para exa-
minar alguns dos princípios de desenho envolvidos na criação de componentes
de software.1
A quarta seção examina o desenho interno orientado a objeto de um com-
ponente com consideráveis detalhes, estudando as interações entre as interfa-
ces de um componente e as classes e os objetos no interior do mesmo. A quinta

1. Se quiser consultar um excelente ensaio sobre a tecnologia de componentes e outros assuntos


pertinentes aos mesmos, recomendo Szyperski, 1998.

384
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 385

seção analisa as questões que determinam uma escolha básica na arquitetura


de componentes: criando componentes leves ou pesados.
A seção final explora as vantagens e desvantagens da utilização de com-
ponentes, especialmente dos adquiridos a partir de distribuidores externos.
Conforme revelado por essa seção, a decisão de “se partir ou não para os com-
ponentes” envolverá a ponderação de diversos fatores antagônicos para deter-
minar se a balança em seu local de trabalho pende a favor ou contra os
mesmos.

15.1 O Que É um Componente?


Bem, agora que você pensou estar livre para ignorar os experts que foram de-
safiados no tocante à terminologia na Parte I (aqueles que se digladiaram so-
bre a definição de orientação a objeto), não os esqueça... pois eles estão de
volta! Desta vez eles estão se esforçando para encontrar o significado de com-
ponente.
Em uma recente conferência denominada “Matéria sobre Objetos e Com-
ponentes”, detive uma turba de experts que passavam pelo saguão e abordei-os
com um pedido simples: “Ei, me desculpem, mas, por favor, poderiam me dizer
o que é um componente?”
Com uma olhadela apressada para trás, muitos deles andaram apressa-
damente, nervosos, como se tivesse pedido para que me dessem cupons de bô-
nus de companhias aéreas. Todavia, eis aqui as respostas dos “especialistas
em componentes” que não se esquivaram de imediato:
• Os componentes são maiores do que os objetos.
• Um componente poderia ser um objeto, mas, por outro lado, ele pode-
ria ser maior. Um componente poderia ser uma biblioteca vinculada
dinamicamente (DLL — dynamically linked library).
• Os componentes definitivamente não são objetos. Eles são maiores,
mais complexos e mais reutilizáveis.
• Os componentes são como objetos, porque a complexidade e funciona-
lidade deles são suportadas por meio de uma interface.
• Qualquer coisa que suporte linguagem de definição de interface (IDL
- interface-definition language) é um componente. Eu suponho que po-
deria ser um objeto.
• Um componente apresenta código binário; ele não precisa ser compi-
lado.
• Os objetos são gerados. Um componente não pode ser gerado.
386 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

• Os componentes são unidades independentes de código que provêm


funcionalidade específica. (Esta afirmação foi de um funcionário da
BloatoSoft, Inc. Quando eu o persegui, querendo saber o que especifi-
camente estava excluído dessa definição, ele desapareceu.)

Minha frustração foi finalmente atenuada por um competente “especia-


lista de componentes”, que me forneceu uma definição composta de oito partes
(e também alguns cupons de bônus de primeira classe).
De acordo com esse expert, um componente de software:

1. tem uma interface externa distinta da implementação interna das opera-


ções do componente declarada por essa interface.
2. tem uma interface definida de maneira contratual: cada uma das opera-
ções dos componentes é definida em termos de sua assinatura (seus tipos
de argumentos de dados de entrada e de saída) e de suas precondições e
pós-condições;
3. não é gerado em diversas cópias, em que cada reprodução tenha seu pró-
prio e característico estado;2
4. demanda certo conjunto de operações (freqüentemente denominadas de-
pendências de contexto [context dependencies] a partir do meio no qual
ele é implantado);
5. provê certo conjunto de operações demandado pelo meio no qual ele é im-
plantado;
6. pode interagir com outros componentes (que seguem padrões compatí-
veis) no mesmo meio, a fim de formar unidades de software de capacidade
arbitrária;
7. é vendido (ou distribuído) sob a forma executável (código binário), em vez
de na forma compilável (código-fonte)
8. pode oferecer publicações (durante o período de desenvolvimento ou no
tempo de execução) das operações que ele suporta, para que outros com-
ponentes as descubram e as utilizem.

2. Note que algumas tecnologias de componentes não insistem sobre essa propriedade. Algumas
permitem gerações de componentes; cada geração com seu próprio estado persistente.
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 387

15.2 Similaridades e Diferenças entre Componentes e


Objetos
Após expressar minha gratidão ao “especialista em componentes”, eu me afas-
tei lentamente, perguntando a mim mesmo quais as diferenças (caso existam)
entre um componente e um objeto. Eu refleti mais tarde: quais as diferenças
entre um componente e uma classe?
Cada uma das oito características anteriores de um componente oferece
sua própria e parcial solução para essas questões:

1. A forte distinção entre a interface de um componente e sua implemen-


tação promove o encapsulamento, o fundamento da orientação a objeto
que introduzi na seção 1.1. Conseqüentemente, de forma semelhante a
uma boa interface de classe, conforme discuti na seção 8.2.3, uma boa
interface de componente limita a congeneridade pelas fronteiras do en-
capsulamento.
2. Mais uma vez, a propriedade contratual das operações em interfaces
de componentes é idêntica à propriedade das operações que discuti na
seção 10.5.
3. As classes podem gerar objetos, sendo que cada um destes tem seu estado
próprio, mutável. Os componentes, por outro lado, tendem a evitar a ma-
nutenção de um estado persistente. Isso significa que as diversas cópias
de um componente (distintamente dos diversos objetos de uma classe) se-
rão idênticas quanto aos valores internos bem como quanto à estrutura.
(Uma ligeira exceção para esse caso é quando um componente é prefixado
com certos valores padrões, conforme analiso abaixo.)
Um componente que não consegue ser gerado é então muito mais uma
utilidade que discuti na seção 3.8 do que uma classe clássica. Como talvez
você recorde, você pode considerar uma utilidade uma classe isenta de ob-
jetos, cujas operações são todas operações de classes. Uma vez que muitas
tecnologias voltadas a componentes impossibilitam a herança entre eles,
a correspondência entre componentes e utilidades (as quais normalmente
se afastam da herança) é ainda maior.
4. A orientação a objeto, como tal, não tem qualquer sentido de “meio” em
torno de um objeto. Em outras palavras, o desenho orientado a objeto
clássico não possui a tendência nem de desenhar classes para serem uti-
lizadas em um meio existente nem de desenhar classes para uma situa-
ção de green field. Os desenhistas de componentes, entretanto, devem
considerar o meio no qual o componente funcionará (geralmente denomi-
388 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

nado de recipiente [contêiner]). Como veremos a seguir, um desenhista de


componentes poderá necessitar (ou tirar partido), de serviços oferecidos
por um componente para reduzir a complexidade deste.
5. Embora as especificações da linguagem (ou do local de trabalho e projeto)
possam requerer um conjunto de operações básicas para cada classe, a
orientação a objeto em si não demanda que uma classe satisfaça as ex-
pectativas de um meio em run-time. Um componente, por outro lado, exe-
cuta somente em seu meio designado, e deve normalmente prover um
conjunto de operações controladas por esse meio.
Esse tipo de controle pode ser técnico ou de negócio (ou de ambos). Um
exemplo de um mandato técnico: todo e qualquer componente deverá su-
portar uma contagem de referências em si próprio, de forma que ele po-
derá ser removido da memória quando não mais for referenciado. Um
exemplo de um mandato de negócio: um componente que lide com produ-
tos físicos em um negócio de comércio eletrônico (e-commerce) baseado na
Web, deverá suportar operações para apresentar as fotos dos produtos
sob a forma de imagens no formato JPEG (Joint Photographic Experts
Group).
6. O software baseado em componente compartilha um importante propósi-
to com o software orientado a objeto: reutilização. O desenho orientado a
objeto combina e recombina objetos de várias classes, cada qual com fun-
cionalidade limitada, em estruturas de maior grandeza e com habilidade
mais sofisticada do que qualquer classe individual.
O desenho de componentes efetua o mesmo com os componentes, mas
a padronização de um dado meio de componente permite ao desenhista
incorporar componentes existentes (ou de fácil aquisição) em um desenho.
Isso constitui uma vantagem pelo fato de que evita uma grande quanti-
dade de programação para implementar o desenho. Obviamente, o dese-
nho orientado a objeto poderia colher o mesmo benefício favorável se
pudessem ser encontradas classes preexistentes, capazes de prontamente
operar juntas, de acordo com as normas referentes ao meio, que forçam
a interoperabilidade entre componentes.
7. Já que os componentes são normalmente distribuídos como programas
executáveis em run-time, eles correspondem muito mais a objetos (que
são unidades de software em run-time) do que a classes (que são unida-
des de software em período de desenvolvimento). Além disso, as normas
relativas a componentes são tipicamente normas sobre código binário,
executável. A aderência a um padrão binário permite aos componentes se
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 389

interligarem, via suas interfaces, sem serem molestados por idiossincra-


sias da linguagem fonte. E, ainda mais, potencialmente permite aos com-
ponentes escritos em diferentes linguagens-fonte interagirem. Por
conseguinte, dois componentes escritos em diferentes linguagens, e/ou
compilados a partir de compiladores diferentes, ainda assim podem tra-
balhar juntos —; contanto que o código compilado siga o padrão binário.
Todavia, talvez não se consiga atingir esse objetivo na vida real. Re-
centemente vi um exemplo no qual o compilador de C++ de um distribui-
dor automaticamente gerava código executável que obedecia à norma do
meio, enquanto o código compilado de outro vendedor necessitava de cer-
to “empurrão” antes de conseguir obedecer.
8. Algumas normas de componentes permitem que outra unidade de softwa-
re inquira um componente em run-time acerca das operações por ele im-
plementadas. Na prática, um componente responderá com as interfaces
que ele suporta, interfaces essas que constituem (da mesma forma que
para uma classe) uma família de operações afins para o componente.
Incidentalmente, alguns componentes aparecem com operações prees-
tabelecidas — operações essas que podem ser invocadas no período de de-
senvolvimento para confirmar defeitos em run-time dentro do
componente. Por exemplo, um argumento de uma operação preestabele-
cida poderá fazer com que o componente de produto relativo ao comércio
eletrônico mencionado anteriormente proporcione uma imagem sob o for-
mato JPEG. De maneira alternativa, um desenvolvedor poderia prefixar
o formato de imagens em GIF ou TIFF. Naturalmente, se não tiver acesso
ao componente durante o período de desenvolvimento, então você não
será capaz de invocar suas operações prefixadas.

15.3 Exemplo de um Componente


O exemplo nesta seção corresponde a GerenciadorDeRecursos, um componente
de negócio destinado ao gerenciamento de recursos. Para ser mais concreto, o
componente GerenciadorDeRecursos poderia ser utilizado para organizar uma
conferência, a qual é uma confluência dos recursos que incluem funcionários,
uma sala de conferência e um projetor suspenso. Entretanto, visto que o com-
ponente é neutro quanto aos tipos de recursos envolvidos, você poderia, igual-
390 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

mente, utilizá-lo para montar um consórcio (pool) de caminhonetes, em que


os recursos seriam funcionários e caminhonetes.3
Conforme exibido pelo diagrama de componente da UML, na Figura 15.1,
GerenciadorDeRecursos suporta quatro interfaces de negócio (ServiçosDeTipoDe-
Recurso, ServiçosDeInstânciaDeRecurso, ServiçosDeAgrupamentoDeRecursos e Ser-
viçosDeCalendário), bem como uma interface técnica (ServiçosPadrões).

Figura 15.1 O componente GerenciadorDeRecursos.

Agora, descreverei brevemente a finalidade de cada interface, juntamente


com alguns de seus mais significativos atributos e operações, como mostrado
nos diagramas de interface da UML, nas Figuras de 15.2 a 15.6.
O estereótipo «interface» constitui uma palavra-chave em um “símbolo de
classe”, denotando que o símbolo efetivamente representa uma interface, e
não uma classe. Visto que uma interface tem operações, mas não atributos,
está ausente o retângulo normal destinado a atributos. As operações que a in-
terface suporta são listadas no retângulo destinado a operações. Entretanto,
a hipótese que a UML defende é que os métodos implementadores dessas ope-
rações residam em algum outro lugar. Em decorrência, o estereótipo «interfa-
ce» é similar ao estereótipo «tipo» que introduzi na seção 11.1, uma vez que
ele representa uma abstração, e não uma implementação. Um símbolo que é
estereotipado com «tipo», entretanto, poderá suportar atributos.
A interface ServiçosDeTipoDeRecurso (veja a Figura 15.2) proporciona ope-
rações que pertencem ao grupo total dos tipos de recursos, grupo esse que po-

3. Como é normal ocorrer em um exemplo em um livro, a necessidade de ser conciso forçou-me


a omitir alguns detalhes de negócio. Por exemplo, não trato de importantes questões, como a
da capacidade da sala de reuniões ou da caminhonete. Deixo esses aperfeiçoamentos para o
leitor. Veja o exercício 5, no final deste capítulo.
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 391

derá incluir Funcionário, Sala e Caminhonete. Exemplos incluem obterNúmero-


Total (o número de tipos de recursos no grupo inteiro) e obterNúmeroDeInstân-
cias, que fornece, por exemplo, o número de funcionários. (Observe que, uma
vez que ServiçosDeTipoDeRecurso opera com um grupo inteiro de tipos de recur-
sos, obterNúmeroDeInstâncias requer um argumento de entrada de dados, id,
para especificar que tipo de recurso deverá ser incluído em instâncias).

«interface»
ServiçosDeTipoDeRecurso
obterNúmeroTotal(): NúmeroInteiro
obterNúmerosDeInstâncias (id: IdDoTipoDeRecurso), NúmeroInteiro
obterNome (id: IDDoTipoDeRecurso, nome: String): out nome: String): Booleano
especificarNome (id: IDDoTipoDeRecurso, nome String): Booleano
éPresente (nome: String): Booleano
obterID (nome: String, out id: IDDDoTipoDeRecurso): Booleano
obterÉHumano (id: IDDoTipoDeRecurso): Booleano
especificarÉHumano (id: IDDoTipoDeRecurso, éHumano): Booleano
acrescentar (nome: String, out id: IDDoTipoDeRecurso): Booleano
remover (id: IDDoTipoDeRecurso): Booleanmo
...

Figura 15.2 As operações de ServiçosDeTipoDeRecurso.

A razão pela qual cada tipo de recurso tenha um ID (como 1), bem como
um nome (como Funcionário) é para facilitar a alteração do nome do tipo de re-
curso. As duas operações obterID e obterNome permitem traslados entre ID e
nome. Se a operação obterÉHumano retorna true, então as instâncias desse tipo
de recurso podem se comunicar e tomar decisões (por favor, sem qualquer ci-
nismo dilbertiano).
A operação acrescentar permite que novos tipos de recursos sejam acres-
centados e inicializados. Ela não faz coisa alguma e retorna um valor false se,
por exemplo, o tipo de recurso já existir. Criar o componente GerenciadorDeRe-
cursos para sua primeira utilização implica invocar repetidamente acrescentar
até que todos os tipos de recursos requeridos estejam presentes.4
A operação remover permite que um tipo de recurso desnecessário seja
eliminado. Ela não faz nada e retorna um valor false se ainda existir a pre-
sença de instâncias desse tipo de recurso. (Para evitar esse problema, você po-
deria primeiramente invocar removerTodasDeUmTipo, que descrevo a seguir.)

4. Repare que exibo simplesmente um argumento de entrada de dados para acrescentar, a saber
nome: String. Na prática, são necessários outros argumentos, tais como éHumano: Booleano.
392 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Incidentalmente, a operação remover nos proporciona uma boa oportunidade


para examinarmos a natureza contratual de uma operação em uma interface
de componente; a segunda característica de um componente que relacionei an-
teriormente.

GerenciadorDeRecursos:: ServiçosDeTipoDeRecurso.remover (id:


IDDoTipoDeRecurso): Booleano
Precondição: obterNúmeroDeInstâncias (id: IDDoTipoDeRecurso) = 0
Pós-Condição: remover = true;
obterNúmeroTotal = old obterNúmeroTotal – 1;

Em primeiro lugar, no alto da Figura encontra-se o nome plenamente


qualificado da operação, juntamente com sua assinatura. A seguir vem sua
precondição — a qual deverá ser verdadeira para que a operação efetivamente
concorde em operar. Nesse caso, não deverá haver instâncias do tipo de recur-
so. Finalmente, a pós-condição declara que a operação de estilo funcional re-
torne o valor true e que o número total dos tipos de recursos diminuam uma
unidade.
A interface ServiçosDeInstânciaDeRecurso (veja a Figura 15.3) é similar sob
certos aspectos a ServiçosDeTipoDeRecurso, exceto por aquela prover operações
que pertencem ao grupo das instâncias de recursos, em vez de pertencer ao
grupo dos tipos. (As instâncias de recursos podem incluir: Sala-101, Sala-12A,
A Sala Mafeking, O Projetor Suspenso no 94, Hugh Jampton, Daisy Weale,
Loomis Ganderbody, Barney Schutt, Mustapha Bobortu e assim por diante.)
Novamente, para facilitar as alterações de nomes, cada instância tem um ID
(identificador), bem como um nome.
A operação obterTipo retorna o tipo de recurso (sob a forma do ID do tipo
de recurso) da instância de recurso. A operação obterEndereçoDeE-Mail retorna
o endereço eletrônico de qualquer instância de recurso que tenha um endereço
(incluindo alguns dos, recursos humanos, mas nem todos). A operação obterÉ-
Disponível retorna um valor de Disponibilidade (disponível, indisponível, desco-
nhecido) para uma dada instância de recurso em determinado horário e data.
As operações acrescentar e remover trabalham muito semelhantemente a
suas correspondentes em ServiçosDeTipoDeRecurso, exceto, é claro, em nível de
instância. Para criar GerenciadorDeRecursos para utilização, você deverá repe-
tidamente invocar ServiçosDeTipoDeRecurso.acrescentar, simultaneamente,
para cada instância de recurso de que você necessitar.
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 393

«interface»
ServiçosDeInstânciaDeRecurso
obterNúmeroTotal(): NúmeroInteiro
obterNome (id: IDDaInstânciaDeRecurso), out nome: String:) Booleano
especificarNome (id: IDDaInstânciaDeRecurso, nome: String:) Booleano
obterTipo (id: IDDaInstânciaDeRecurso, out tipo: IDDoTipoDeRecurso): Booleano
especificarTipo (id: IDDaInstânciaDeRecurso, tipo: (IDDoTipoDeRecurso): Booleano
obterID (nome: String, out id: IDDaInstânciaDeRecurso): Booleano
obterEndereçoDeE-Mail (id: IDDaInstânciaDeRecurso): String
especificarEndereçoDeE-Mail (id: IDDaInstânciaDeRecurso, endereçoDeE-Mail:
String)
obterÉDisponível
(id: IDDaInstânciaDeRecurso, horário: Horário/Data): Disponibilidade
especificarÉDisponível
(id: IDDaInstânciaDeRecurso, horário: Horário/Data, disponível: Disponibilidade)
acrescentar (...): booleano
remover (id: IDDaInstânciaDeRecurso): Booleano
removerTodas ()
removerTodasDeUmTipo (id: IDDoTipoDeRecurso)
...

Figura 15.3 As operações de ServiçosDeInstânciaDeRecurso.


A operação removerTodas é uma operação exagerada que remove cada ins-
tância de recurso de uma vez. Uma versão mais amena é removerTodasDeUm-
Tipo, que se desfaz da população de instâncias de somente um tipo de recurso.
(Por exemplo, invocar removerTodasDeUmTipo, com o ID do tipo de recurso de
Funcionário como argumento, eliminaria Jim Spriggs, Brunhilde Schreck, An-
gus Podgorny, juntamente com as instâncias de Funcionário relacionadas an-
teriormente.)
A próxima interface a explorarmos é ServiçosDeAgrupamentoDeRecursos
(veja a Figura 15.4). Essa interface pode também ser denominada ServiçosDe-
Conferência, exceto quando desejarmos que o componente GerenciadorDeRecur-
sos seja mais geral — para gerenciar recursos distintos dos utilizados para
conferências. (Entretanto, se ajudar, você poderá pensar em um grupo de re-
cursos como uma conferência).
A operação obterStatusDeProgramação retorna o atual status de progra-
mação do grupo de recursos como um valor de StatusDoGrupoDeRecursos ou
como: programadasTodas (programadas, com todas instâncias de recursos dis-
poníveis); programadasApenasObrigatórias (programadas, com algumas instân-
cias de recursos não obrigatórias indisponíveis); ou não programadas (não
programadas atualmente). A operação obterHorário retorna a data e o horário
do grupo de recursos (caso este atualmente esteja programado).
394 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

«interface»
ServiçosDeAgrupamentoDeRecursos
obterNúmeroTotal(): NúmeroInteiro
obterNome (idDoGrupo: IDDoGrupoDeRecursos, out nome: String): Booleano
especificarNome (idDoGrupo: IDDoGrupoDeRecursos, nome: String): Booleano
obter ID (nome: String, out id: IDDoGrupoDeRecursos): Booleano
obterStatusDeProgramação (idDoGrupo: IDDoGrupoDeRecursos): StatusDoGrupo-
DeRecursos
obterHorário (idDoGrupo: IDDoGrupoDeRecursos, horário: Horário/Data): Booleano
criar: IDDoGrupo: IDDoGrupoDeRecursos)
acrescentarRecurso
(idDoGrupo: IDDoGrupoDeRecursos, idDaInstância: IDDaInstânciaDeRecurso,
obrigatória: Booleano): Booleano
liberarRecurso
(idDoGrupo: IDDoGrupoDeRecursos, idDaInstância: IDDaInstânciaDeRecurso):
Booleano
obterHoráriosPossíveis
(idDoGrupo: IDDoGrupoDeRecursoss, início, fim: Horário/Data): RelaçãoDe-
Horários
programar
(idDoGrupo: IDDoGrupoDeRecursos, horário: Horário/Data): StatusDoGrupoDe-
Recursos
nãoprogramar (idDoGrupo: IDDoGrupoDeRecursos)
...

Figura 15.4 As operações de ServiçosDeAgrupamentoDeRecursos.

A operação criar cria um novo grupo de recursos. (O termo criar parece


ser mais adequado do que acrescentar para essa operação.) Ela retorna um va-
lor de IDDoGrupoDeRecursos (tal como 142857), que servirá como o identifica-
dor característico do grupo de recursos que está sendo iniciado. A operação
criar não faz nada além disso; outras operações, mais adiante, constroem e ma-
nipulam o grupo de recursos.
A operação cancelar se livra de um grupo de recursos (e faz com que
quaisquer instâncias de recursos vinculadas fiquem disponíveis novamente).
Na terminologia própria das conferências, cancelar cancela uma conferência.
Para especificar que instâncias de recursos você precisa para o grupo de re-
cursos, você convoca a operação acrescentarRecurso diversas vezes, simultanea-
mente, para cada instância de que você necessitar. Você precisará especificar se
uma instância de recurso é ou não obrigatória (o que significa que o grupo não
poderá ser programado à época em que essa instância estiver indisponível). A
operação acrescentarRecurso retorna false se a instância já faz parte do grupo.
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 395

A operação liberarRecurso remove uma instância de recurso de um grupo e re-


torna false se a instância não faz parte atualmente do grupo.
A operação obterHoráriosPossíveis retorna uma relação de todas as da-
tas/horários em que cada instância de recurso em um grupo está disponível.
A relação abarca apenas a faixa especificada pelas duas datas/horários, início
e fim. (Na prática, cada data/horário da relação deveria ser marcada com um
status, identificando caso todos os recursos estejam disponíveis, caso apenas
se encontrassem os recursos obrigatórios; aqui não fiz isso).
A operação programar programa uma data/horário para as instâncias no
grupo de recursos se reunirem. Ela retorna um valor de StatusDoGrupoDeRe-
cursos, que indica que, em determinada data e horário, todas as instâncias de
recursos estão disponíveis; apenas as instâncias obrigatórias estão disponí-
veis; ou algumas instâncias obrigatórias estão indisponíveis. A operação não
programar coloca um grupo de recurso em estado de interrupção (elimina sua
data/horário programada) e desobriga as instâncias de recursos envolvidas de
seus compromissos nessa data e horário.
A quarta interface de negócio do componente GerenciadorDeRecursos é Ser-
viçosDeCalendário (veja a Figura 15.5), que trata de questões gerais sobre da-
tas e horários. Por exemplo, a operação obterMenorIncrementoDeTempo
retorna o valor do menor incremento de tempo que o componente reconhece.
(Você poderá prefixá-lo como padrão — talvez os já famosos 15 minutos — no
período de desenvolvimento, embora você ainda pudesse alterar esse valor pa-
drão em run-time via especificarMenorIncrementoDeTempo.)

«interface»
ServiçosDeCalendário
obterMenorIncrementoDeTempo(): NúmeroInteiro
especificarMenorIncrementoDeTempo (incrementoDeTempo: NúmeroInteiro)
especificarDisponibilidadeDeDefault (data: Data, status: Disponibilidade)
...

Figura 15.5 As operações de ServiçosDeCalendário.

A operação especificarDisponibilidadeDeDefault oferece um meio de se ins-


tituirem “feriados” no calendário. Você provê isso com uma data, e ela marca-
rá a disponibilidade inicial de todas as instâncias de recursos nessa data
como, digamos, indisponível. Não há dúvidas de que ServiçosDeCalendário tem
muitas outras operações — Menciono algumas delas no exercício 3 a seguir —,
396 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

mas, para ser breve, vamos nos mover adiante para a interface final do com-
ponente GerenciadorDeRecursos.
A interface ServiçosPadrões (veja a Figura 15.6) proporciona as operações
que todos os componentes devem ter por razões técnicas (mais do que por razões
de negócio). Cada tecnologia de componente possui um conjunto diferente dessas
operações; os próximos parágrafos descrevem um desses conjuntos típicos.

«interface»
ServiçosPadrões
obterContagemDeReferências(): NúmeroInteiro
obterIdentificador
(cliente: Cliente, nome: NomeDeInterface,
out identificador: IdentificadorDeInterface): Booleano
desconectar (cliente: Cliente, identificador: IdentificadorDeInterface): Booleano
...

Figura 15.6 As operações de ServiçosPadrões.


A operação obterContagemDeReferências retorna o número de referências
que outro software fez para esse componente. (Em muitas tecnologias são
mantidas contagens de referências separadas para cada interface do compo-
nente.) Quando o componente está sendo utilizado — e, portanto, obterConta-
gemDeReferências retorna um valor positivo diferente de zero —, o componente
deve ser retido como um programa executável na memória principal.5 Quando
ninguém está utilizando o componente — e, portanto, obterContagemDeRefe-
rências retorna zero —, o componente pode ser suprimido.
O cliente de um componente poderá obter o identificador de uma interface
de componente invocando obterIdentificador. Esta retorna um identificador para
a interface, o qual é muito parecido com o identificador de um objeto padrão visto
que constitui o número de que “você necessitará em toda correspondência futura”
com a interface (tal como invocar uma operação dessa interface).
Entretanto, pode ser que alguns clientes não sejam privilegiados o
bastante para acessar uma dada interface de um componente. Por exem-
plo, somente clientes muito privilegiados de GerenciadorDeRecursos talvez
recebam autorização para utilizar a interface de ServiçosDeTipoDeRecurso.
A outros clientes, caso eles peçam um identificador dessa interface, não
será fornecido nada; obterIdentificador retornaria um valor false. (Em con-

5. Ou, conforme componentes mais filosóficos propuseram, “Me utuntur, ergo exto”. (Estou sendo
utilizado, portanto existo.)
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 397

trapartida, a interface ServiçosDeAgrupamentoDeRecursos talvez esteja dispo-


nível a todos os clientes, independentemente das graduações dos mesmos.)
A operação conectar informa a um componente que outra unidade de soft-
ware está, nesse momento, utilizando uma interface. (Essa operação poderá
aumentar em 1 a contagem de referências desse componente; alternativamen-
te, obterIdentificador poderia fazer esse trabalho.) A operação conectar retor-
nará false se, por exemplo, o identificador provido for incorreto ou um cliente
não muito privilegiado tiver de alguma maneira obtido um valor válido para
o identificador. A operação desconectar é o inverso; ela diminui de 1 a contagem
de referências do componente.
Agora, já que examinamos as interfaces externas de GerenciadorDeRecur-
sos, vamos investigar o desenho interno desse componente.

15.4 Desenho Interno de um Componente


Como salientei na característica 3 da seção 15.1, normalmente um componen-
te não aparece sob a forma de diversas gerações, na condição de estados, de
maneira que você não é obrigado a utilizar orientação a objeto para desenhar
seus componentes. Você poderia, por exemplo, desenhar cada operação de uma
interface como um procedimento ímpar, simples, e desenhar os elementos in-
ternos do componente do mesmo modo. Isso certamente seria desenho estru-
turado.
Entretanto, assumindo que você queira acumular as vantagens ofereci-
das pela orientação a objeto que discuti nos Capítulos 1 e 2, abordarei o dese-
nho interno de GerenciadorDeRecursos de uma maneira orientada a objeto.
Primeiro, vamos examinar a arquitetura de pacote global para Gerencia-
dorDeRecursos e seu software assistente, conforme mostrado na Figura 15.7.6
(Eu examinarei a seguir os conteúdos de cada pacote com mais detalhes.)
No nível mais alto, vemos a estrutura de domínio tratada na seção 9.1: o
domínio de aplicação inclui as quatro interfaces da seção 15.3. Nesse diagra-
ma, omiti o estereótipo «interface» para reduzir a confusão.7 Igualmente omiti
ServiçosPadrões inteiramente porque, uma vez que essa interface está presente
em todos os componentes, ela não acresce nada de valor a esse exemplo em
particular.

6. Introduzi a notação de pacote da UML na seção 7.1.1.


7. Na prática, você provavelmente iria desenvolver uma classe “sombra” para cada interface, a
qual residiria no domínio de aplicação. Por exemplo, você poderia criar uma classe Serviços-
DeTipoDeRecurso que proporcionaria os métodos para implementar as operações na interface
ServiçosDeTipoDeRecurso. Com essa abordagem, cada um dos retângulos no domínio de apli-
cação da Figura 15.7 representaria, em efeito, tanto uma interface quanto a classe implemen-
tadora desta.
398 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Figura 15.7 Diagrama de pacote da UML para o software


e de gerenciamento de recursos.

O domínio de negócio contém dois pacotes: um para o subdomínio de re-


cursos e um para o subdomínio de calendário. Cada um desses pacotes contém
diversas classes, que examinaremos a seguir.
Eu dividi o domínio de arquitetura em duas partes. O domínio de arqui-
tetura dependente de plataforma inclui utilidades (provavelmente adquiridas
de um distribuidor) que são escritas especificamente para uma única platafor-
ma (uma combinação entre um hardware e um software). O domínio de arqui-
tetura independente de plataforma inclui uma versão das mesmas utilidades,
que (do ponto de vista dos clientes de utilidades) não é específica de qualquer
plataforma. Em outras palavras, o domínio de arquitetura independente de
plataforma é uma “camada de software” que protege os clientes de utilidades
das peculiaridades das operações efetivas de software, envolvendo e-mail e
banco de dados, providas com a plataforma.
Cada um dos domínios de arquitetura, dos tipos dependente e indepen-
dente de plataforma, contém dois pacotes: um para a biblioteca de utilidades
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 399

referentes a e-mail, e o outro para a biblioteca de utilidades referentes a banco


de dados.
Agora, vamos retornar ao domínio de negócio para examinar o conteúdo
do PacoteDeRecursos.
A Figura 15.8 mostra as principais classes e associações dentro de Paco-
teDeRecursos. Você talvez tenha notado que a Figura 15.8 segue o mesmo mo-
delo de dois diagramas de classe vistos anteriormente: Figura 11.6 (para
Espécie e Animal) e Figura 11.7 (para LinhaDeProduto e ItemDeProduto). Isso
não é nenhuma coincidência: todos os três diagramas de classe lidam com ti-
pos, cada um dos quais agrega um punhado de instâncias.

Figugra 15.8 Algumas das classes dentro de PacoteDeRecursos.

Precisamos gerar um objeto da classe TipoDeRecurso para cada um dos ti-


pos de recursos no empreendimento. Por exemplo, se os tipos de recursos fos-
sem Funcionário, Sala e QuadroBranco, então teríamos de criar três objetos da
classe TipoDeRecurso (via a operação acrescentar na interface ServiçosDeTipoDe-
Recurso do GerenciadorDeRecursos).
Os métodos implementadores de operações nas interfaces de um compo-
nente fazem uma grande parte de seus trabalhos manipulando classes no do-
mínio de negócio. Para realçar este fato com um exemplo, veja aqui o código
dentro de GerenciadorDeRecursos::ServiçosDeTipoDeRecurso.acrescentar, que cria
um novo tipo de recurso (enviando mensagens para invocar operações da clas-
400 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

se TipoDeRecurso). Observe que muitas das operações de TipoDeRecurso são


operações de classes e, por esse motivo, estão destacadas na Figura 15.8.

operation ServiçosDeTipoDeRecurso.acrescentar (nome: String, out


id: IDDoTipoDeRecurso): Booleano
begin method
novoTipoDeRecurso: TipoDeRecurso;
// este deterá o identificador do objeto criado
id := null;
if TipoDeRecurso.éPresente (nome)
//verifique se name é presente
then return false; // name já se encontra em uso
else novoTipoDeRecurso := TipoDeRecurso.novo (nome);
id :=novoTipoDeRecurso.id;
return true;
endif
end method

Algumas notas explicativas estão em ordem: a assertiva if verifica se o


nome do novo tipo de recurso já existe. Em caso afirmativo, a operação acres-
centar retorna sem êxito. Se tudo está bem, então a operação invoca a opera-
ção de classe novo na classe TipoDeRecurso para criar um novo objeto. O
identificador retornado desse objeto fica armazenado na variável novoTipoDe-
Recurso.
Quando novo cria um objeto, ele também executa duas ações adicionais:
ele consegue um ID singular para o objeto (ao invocar à operação de classe pri-
vada obterPróximoID); e ele armazena todos os atributos do objeto novato Tipo-
DeRecurso como uma linha em uma tabela relacional. (Muito embora eu não
tenha mostrado esta conexão entre a operação novo e um banco de dados ex-
plicitamente em nenhum lugar, fica implícito pela Figura 15.7 como a referên-
cia vertical que vai de todo o DomínioDeNegócio a ServiçosDeBancoDeDados
IndependentesDePlataforma. As operações novo de outras classes também se
aproveitam dos benefícios oferecidos por ServiçosDeBancoDeDadosIndependen-
tesDePlataforma.)
Finalmente, o método para a operação acrescentar encontra e retorna o ID
do objeto TipoDeRecurso recentemente criado ao recorrer a id, uma operação de
instância de acesso do próprio objeto.
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 401

Caso eu não fosse uma pessoa piedosa, e tivesse de lhe apresentar, mais
à frente, detalhes solenes de como as operações da interface de GerenciadorDe-
Recursos interagem com as classes subjacentes no DomínioDeNegócio, confor-
me mostrado na Figura 15.7, examinaria o seguinte:

• as operações remanescentes de ServiçosDeTipoDeRecurso, que princi-


palmente interagem com a classe TipoDeRecurso;8
• as operações das outras interfaces de negócio “de recurso” (Serviços-
DeInstânciaDeRecurso e ServiçosDeAgrupamentoDeRecursos), cada qual
interagindo com diversas das classes em PacoteDeRecursos;
• as operações de ServiçosDeCalendário, cada qual interagindo com clas-
ses no PacoteDeCalendário;
• detalhes de atributos e operações em outras classes do DomínioDeNe-
gócio, muitos dos quais são similares em natureza aos atributos e ope-
rações de amostras que eu tinha revelado para a classe TipoDeRecurso,
na Figura 15.8;
• outras interações entre classes no DomínioDeNegócio e ServiçosDeBan-
coDeDadosIndependentesDePlataforma.

Mesmo poupando-o de tudo isso, existe um aspecto remanescente de Ge-


renciadorDeRecursos que devo analisar. Esse aspecto é a interação entre a in-
terface ServiçosDeAgrupamentoDeRecursos e a utilidade ServiçosDeE-MailInde-
pendentesDePlataforma, interação essa necessária para contatar recursos hu-
manos (pessoas!) para perguntar se elas estão interessadas em participar de
uma conferência. Essa comunicação via e-mail tem implicações significativas
para a interface do componente em si.
Enviar um e-mail é simples. Você apenas compacta o texto da mensagem
com um identificador apropriado — que inclui o endereço do remetente, o en-
dereço do recipiente (descoberto a partir do atributo EndereçoDeE-Mail do ob-
jeto recipiente: InstânciaDeRecurso), e alguma espécie de título da mensagem —
e remete-o para uma operação adequada de ServiçosDeE-MailIndependentesDe-
Plataforma, digamos enviarE-Mail. O título da mensagem poderá incluir o nome
e o horário do grupo; o texto da mensagem poderá incluir as mesmas informa-
ções, seguidas por uma descrição mais minuciosa e de um lugar para se “mar-
car um X” se o recipiente for capaz de participar.

8. Para ser preciso, de forma até pedante, são os métodos que implementam operações nas in-
terfaces os quais interagem com as operações de classe de TipoDeRecurso e operações de ins-
tância dos objetos da classe TipoDeRecurso.
402 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

Entretanto, o problema aparece quando o recipiente responde. Quando a


resposta via e-mail chega ao servidor de e-mails (ou em qualquer outro lugar),
de que forma a interface ServiçosDeAgrupamentoDeRecursos, que enviou a men-
sagem original, sabe se a resposta para a conferência é afirmativa ou negati-
va? A resposta envolve eventos e recados.
Conforme vimos na seção 5.3.2 (na qual exploramos o mecanismo de re-
cado), um objeto signatário interessado em ouvir sobre ocorrências de certo
tipo de evento poderá registrar esse interesse com um objeto listener, que é in-
cumbido de prestar atenção nessas ocorrências. O objeto listener, a seguir, “dá
um recado” ao objeto signatário no caso de haver alguma ocorrência.
O desenho da estrutura de recados entre ServiçosDeAgrupamentoDeRecur-
sos e ServiçosDeE-MailIndependentesDePlataforma requer o seguinte:

1. Um objeto signatário interessado em determinado tipo de evento. Neste


exemplo, a interface ServiçosDeAgrupamentoDeRecursos é que está interes-
sada em receber respostas via e-mail sobre a disponibilidade das instân-
cias de recursos.
2. Um objeto listener, que é capaz de detectar cada ocorrência desse tipo de
evento. Neste exemplo, é o pacote ServiçosDeE-MailIndependentesDePlata-
forma, que não é apenas uma “camada de software” que fornece nomes
mais bonitos ou seqüências mais ordenadas de argumentos para opera-
ções supridas pelo software de PacoteDeE-Mail. Mais do que isso, ele pode
conferir cada mensagem chegada via e-mail para ver se ela satisfaz os
critérios (tais como nome do remetente, nome do recipiente, título e assim
por diante) para qualquer tipo de evento registrado.
3. O meio para o objeto listener aceitar o registro do tipo de evento a partir
do objeto signatário.
4. O meio para o objeto listener informar ao objeto signatário dentro de um
período de tempo razoável sobre cada ocorrência de evento (o recado).
5. O meio para o objeto signatário compreender perfeitamente o texto com-
pleto da mensagem de e-mail que disparou a ocorrência do evento.

Vamos ampliar algumas dessas condições necessárias.


Para satisfazer o requisito 2, precisamos de um vínculo entre ServiçosDeE-
MailIndependentesDePlataforma e PacoteDeE-Mail, visto que o primeiro pode re-
ver cada uma das mensagens de e-mail para determinar se ela atende aos
critérios (como ter o correto recipiente) para qualquer tipo de evento. Isso pode
ser feito se PacoteDeE-Mail em seus próprios termos provê algum tipo de me-
canismo de recado. De outra forma, ServiçosDeE-MailIndependentesDePlatafor-
Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 403

ma necessitaria esquadrinhar todas as mensagens de interesse para ele (ou


melhor, para seus objetos signatários), a fim de detectar ocorrências de even-
tos significativos. Ele poderia fazer isso se tivesse a autorização para recupe-
rar mensagens para certos recipientes, os quais, em seguida, ele examinaria
com cuidado e arquivaria em um banco de dados.
O requisito 3 simplesmente requer uma operação sobre a interface do ob-
jeto listener que aceitará o registro de um tipo de evento de e-mail, inclusive
todos os critérios significativos para avaliar quantitativamente se um recebi-
mento de e-mail constitui ou não uma ocorrência desse tipo de evento. (A in-
terface, ainda, precisa de outra operação para remover do registro.)
Há pelo menos três abordagens de desenho que satisfazem o requisito 4:

• A primeira abordagem segue o desenho literal de recado que vimos na


seção 5.3.2, no qual ServiçosDeE-MailIndependentesDePlataforma invo-
caria uma operação sobre uma interface de GerenciadorDeRecursos,
talvez denominada e-MailRecebido. Poderíamos acrescentar esta ope-
ração, que até o momento está faltando em quaisquer das interfaces
de GerenciadorDeRecursos, na interface ServiçosDeAgrupamentoDeRe-
cursos. Diferentemente de outras operações de ServiçosDeAgrupamen-
toDeRecursos (como acrescentarRecurso ou programar, na Figura 15.4),
essa espécie de operação não provê um serviço para os clientes de um
componente; ela corresponde a uma operação de notificação de
eventos necessária para o próprio componente.9
• A segunda abordagem é uma variação da primeira, na qual o meio em
que os objetos signatário e listener operam pode mediar o recado,
mantendo a lista de signatários registrados para um tipo de evento.
Nessa abordagem, quando um objeto listener detecta uma ocorrência
de evento, ele informa ao meio — talvez um serviço do sistema ope-
racional. Por sua vez, o meio notifica os signatários de que ocorreu o
evento.
• A terceira abordagem de desenho para o requisito 4 é ter o registro
de ServiçosDeE-MailIndependentesDePlataforma em uma tabela toda
vez que se detectar a ocorrência de um evento. Os objetos signatários
iriam, então, periodicamente verificar, ou seja, contar os registros
nesta tabela para seus eventos de interesse. A vantagem desse dese-

9. Como você talvez recorde da seção 1.5.4, uma mensagem que invoca uma operação para in-
formar a um objeto — ou, aqui, uma interface — que algo ocorreu é denominada mensagem
informativa.
404 FUNDAMENTOS DO DESENHO ORIENTADO A OBJETO COM UML

nho é que ele não exige uma operação de notificação de eventos na in-
terface do signatário. A desvantagem é que o objeto signatário perde
um bocado de tempo verificando eventos (se ele contar os registros
freqüentemente) ou ele poderá esperar um tempo longo para saber so-
bre uma ocorrência de evento (se ele contar os registros raras vezes).
Há duas abordagens para se desenhar o requisito 5:

• Em uma abordagem, o objeto listener passa ao signatário todos os de-


talhes sobre a ocorrência de um evento (neste caso, uma mensagem
de e-mail completa).
• Na outra abordagem, o objeto listener simplesmente informa ao sig-
natário a ocorrência de um evento: o objeto signatário tem a função
de recuperar os detalhes da ocorrência específica, presumivelmente
vindos de uma tabela em que o objeto listener os tinha depositado.

Na forma de um resumo gráfico desta seção, a Figura 15.9 ilustra que um


componente provê serviços para outros componentes e, ainda, requer serviços
de outros componentes. Além disso, um componente pode detectar eventos
para outros componentes, e pode, além disso, requerer notificação sobre even-
tos de outros componentes.

Figura 15.9 O que um componente provê e requer.


Cap. 15 DESENHANDO UM COMPONENTE DE SOFTWARE 405

15.5 Componentes Leves e Pesados


Na seção anterior, tratei ServiçosDeE-MailIndependentesDePlataforma como um
pacote: um grupo de classes afins. Entretanto, há boas razões para elevar, por
seus próprios méritos, ServiçosDeE-MailIndependentesDePlataforma à condição
de um componente. Por exemplo, um pacote tipicamente torna visível diversas
classes, cada qual com diversas operações, embora os clientes do pacote talvez
necessitem ver só um pequeno número dessas operações. (Para utilizar da
aritmética simplista como ilustração: o pacote pode conter cinco classes, cada
qual com 10 operações, enquanto apenas 20 dessas 50 operações são úteis aos
clientes.) Ainda, a melhor organização das operações na(s) interface(s) de um
componente poderá diferir pela forma com que essas operações estão dispostas
nas suas próprias classes.
Poderíamos argumentar de maneira similar para elevar ServiçosDeBanco-
DeDadosIndependentesDePlataforma para a condição de um componente. Caso
o fizéssemos, obteríamos a estrutura de componentes mostrada na Figura
15.10, na qual cada retângulo em negrito encerra um único componente.
Nós também precisamos formular — outra questão: por que não combi-
nar ServiçosDeE-MailIndependentesDePlataforma e GerenciadorDeRecursos em
um único componente? Se olharmos unicamente para ServiçosDeE-MailIndepen-
dentesDePlataforma, a resposta é clara. Soterrar um componente útil como
esse dentro de outro seria semelhante a encerrar uma pérola no interior de
uma ostra; qualquer pessoa que desejasse utilizar ServiçosDeE-MailIndependen-
tesDePlataforma teria também de comprar (e carregar em seus computadores)
o software, maior e muito menos reutilizável, de GerenciadorDeRecursos.
Todavia, do ponto de vista de GerenciadorDeRecursos, a resposta não é as-
sim tão óbvia. Pelo fato de termos desenhado GerenciadorDeRecursos para uti-
lizar o componente ServiçosDeE-MailIndependentesDePlataforma como um
serviço de e-mail (