Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Objetivos Específicos
Introduzir os conceitos básicos relacionados com a orientação a objeto, incluindo
sua aplicabilidade a Análise Orientada a Objeto (OOA), o Projeto Orientado a
Objeto (OOD) e a Programação Orientada a Objeto (OOP)
Apresentar a linguagem de modelagem padrão de mercado Unified Modeling
Language (UML) largamente utilizada para a Análise e Projeto de sistemas de
software de grande escala
Apresentar um processo completo de desenvolvimento de software utilizável com a
linguagem UML
Exemplificar OOA, OOD, OOP, UML e o processo de desenvolvimento através de um
estudo de caso completo
Apresentar técnicas básicas de Projeto Orientado a Objeto. O enfoque é mostrar
em que consiste um bom projeto.
Apresentar Padrões de Projeto, chaves para o bom projeto de software.
Apresentar rapidamente assuntos mais "quentes" relacionados com a tecnologia
OO.
Permitir que o aluno aprofunde seu conhecimento dos conceitos apresentados
através da elaboração de um sistema completo
apoo-1 programa próxima
3. Fase de Análise 1
4. Fase de Projeto 1
5. Fase de Implementação
6. Fase de Análise 2
7. Fase de projeto 2
7.1 Polimorfismo
7.2 Interfaces
7.3 Composição versus herança
7.4 Padrões de Projeto (Design Patterns)
8. Tópicos avançados
8.1 Refactoring
8.2 Extreme Programming
8.3 Frameworks
8.4 Componentes
apoo-2 home
Segunda alternativa:
A análise consiste de todas as atividades feitas com ou para o conhecimento
do cliente. A informação produzida é aquela que o cliente deve discutir e
aprovar
O projeto inclui as atividades que resultam em informação que interessa
apenas ao programador
Com essa definição, a análise invade um pouco o "lado da solução", pois o
cliente deve discutir alguns tipos de interações que ocorrerão na interface
do usuário, etc.
Observe portanto que não definição binária que isole "análise" de "projeto"
Nesta disciplina, adotaremos a segunda alternativa, pois queremos associar as
palavras "análise" e "projeto" aos artefatos (deliverables) entregues nos final de
cada fase
Um modelo de análise deve ser aprovado pelo cliente e pode incluir alguma
(pequena) discussão da solução, principalmente no que diz respeito à
interface com usuário, etc.
Apesar do nome da disciplina, vamos ver também as fases de requisitos,
implementação e testes
A obtenção de requisitos é frequentemente incluída na fase de análise ("análise
de requisitos")
Modelos e Artefatos
Objetivos
Definir os modelos e artefatos a serem produzidos durante o proceso de
desenvolvimento de software
Apresentação
Interface gráfica, janelas
Lógica de aplicação - Objetos do domínio de problema
Objetos representando conceitos do domínio (business objects)
Contém o que se chama também business logic
Lógica de aplicação - Objetos de serviço
Objetos que não pertencem ao domínio do problema mas oferecem serviços
tais como interfaceamento para um banco de dados, etc.
Armazenamento
Mecanismos de armazenamento persistente (SGBDOO, SGBDR, SGBDOR)
Usam-se Análise e Projeto OO principalmente nas camadas de lógica de aplicação
Falaremos em mais detalhes sobre arquiteturas em camadas na seção 4.2 (Projeto
arquitetural)
Boa parte da disciplina tratará do business logic
A seção 4.9 tratará de objetos para serviços de persistência
plan-1 programa próxima
Entendendo Requisitos
Objetivos
Descrever os artefatos do processo de desenvolvimento relacionados aos requisitos
Identificar e categorizar requisitos funcionais do sistema
Identificar e categorizar requisitos não-funcionais do sistema (atributos do sistema)
Requisitos
Requisitos são uma descrição de necessidades ou desejos para um produto.
Motivo da fase de levantamento de requisitos: saber que sistema deve ser construido
Se houver sucesso no levantamento de requisitos, não haverá surpresas desagradáveis para o
usuário na entrega do sistema
Artefatos típicos a serem criados
Breve descrição do sistema
Descrição dos clientes alvo
Pode incluir como eles operam hoje e quais problemas eles enfrentam
Descrição das metas do sistema
Descrição dos requisitos funcionais do sistema
Descrição dos requisitos não funcionais do sistema
Usaremos o estudo de caso (Terminal PDV) para exemplificar a obtenção de requisitos
de impostos
Funcionalidade básica
A lista abaixo não é exaustiva mas servirá de exemplo para explicar as atividades do
processo de desenvolvimento
Observe que o levantamento dos requisitos funcionais deve ser completado usando use
cases (discutidos no próximo capítulo)
Use cases descrevem os processos do domínio do problema
A lista abaixo pode servir para nortear o levantamento de use cases
Alguns desenvolvedores usam apenas use cases para levantar os requisitos funcionais
De toda forma, a tabela abaixo exemplifica o que se entende por requisito funcional
Referência Funcionalidade Categoria
R1.1 Registrar a vanda corrente (os itens comprados) Evidente
Calcular o total de venda, incluindo impostos e descontos
R1.2 Evidente
aplicáveis
Capturar a informação do item sendo comprado através
de um leitor de código de barra, ou manualmente usando
R1.3 Evidente
um código de produto tal como o Universal Product Code
(UPC)
R1.4 Dar baixa no inventário ao terminar uma venda Escondida
R1.5 Manter um log de vendas feitas Escondida
O caixa deve fazer login com uma identificação e uma
R1.6 Evidente
senha antes de usar o sistema
Deve prover um mecanismo persistente de
R1.7 Escondida
armazenamento
R1.8 Prover integração com outros sistemas Escondida
R1.9 Exibir a descrição e o preço do item sob consideração Evidente
Tolerância a falha
Desempenho
Segurança
Compatibilidade com outros produtos/versões e necessidades de migração
Necessidades de internacionalização do produto
Necessidades de customização do produto pelo cliente
Suporte
Preço da solução
Documentação necessária
Uso de padrões
Aspectos legais
Integração com outros produtos
Packaging
Requisitos de instalação
Riscos aceitáveis
Os requisitos não funcionais também são chamados de atributos do sistema
Alguns exemplos seguem
Atributo Detalhes ou condição limite
(Condição limite) Ao registrar um item sendo vendido, a descrição e preço devem aparecer
Tempo de resposta
em 2 segundos
(Detalhe) Usar formulários para entrada de dados e dialog boxes
Tipo de interface
(Detalhe) Maximizar a facilidade de uso via teclado e não via mouse
(Condição limite) Deve fazer log dos pagamentos autorizados via cartão de crédito em 24
Tolerância a falhas
horas, mesmo com falhas de energia ou de dispositivo
Plataformas operacionais (Detalhe) Microsoft Windows 95, Windows, 98, Windows NT e Windows 2000
Introdução
Use cases são usados para descrever os processos do domínio de problema
São uma excelente forma de explorar e documentar os requisitos funcionais
Antes de elaborar use cases, pode valer a pena elaborar uma tabela de funções básicas como vimos na
seção anterior
Use cases
Um use case é um documento narrativo que descreve uma sequência de eventos feitos por um ator no
uso de um sistema para completar um processo de interesse deste ator.
Use cases são "estórias" ou "casos" no uso de um sistema
As estórias acabam revelando as funcionalidade desejada do sistema
Em UML, um use case é representado assim:
UML não força o formato exato de um Use Case. A clareza na descrição é o essencial.
Iniciamos acima com um Use Case de alto nível, fornecendo poucos detalhes
Útil para entender rapidamente os processos principais envolvidos
1. O Use Case inicia quando um cliente chega a um caixa munido de TPDV com
itens a comprar
Se houver mais itens, o caixa pode informar a quantidade também A descrição e preço do item corrente são exibidos
4. Ao completar a entrada dos itens, o caixa indica este fato ao TPDV 5. Calcula e apresenta o total da venda
Sequências alternativas:
Linha 2: Entrada de um identificador inválido. Indica erro.
Linha 7: Cliente não tinha dinheiro suficiente. Cancela transação de venda.
Atores
Um ator é uma entidade externa ao sistema que, de alguma forma, participa das estórias relatadas no Use
Case
Um ator tipicamente estimula o sistema com eventos de entrada ou recebe algo so sistema
Atores são representados pelo papel que desempenham (e não por nome de pessoa, etc.)
Em UML, o ícone que representa um ator é o seguinte:
Para cada Use Case, um dos atores é o iniciador e outros atores podem ser participantes
Atores correspondem frequentemente a papeis de seres humanos mas, às vezes, outros sistemas ou
dispositivos elétricos ou mecânicos podem ser atores
Introdução
De forma geral, cada iteração da fase de construção vai tratar de um ou mais Use Cases identificados
Às vezes, um Use Case pode ser tratado em duas ou mais iterações, com versões simplificadas tratadas
primeiro
O estudo de caso
Uma possibilidade de priorização
Prioridade Use Case Justificativa
Alta Comprar itens Satisfaz quase todos os critérios de aumento de prioridade
Adicionar novos usuários Afeta a segurança
Média Login Afeta a segurança
Devolver itens Processo importante; afeta a contabilidade
Fechamento de caixa Efeito mínimo na arquitetura
Baixa Start Up Definição depende de outros Use Cases
Shut Down Efeito mínimo na arquitetura
O Use Case Start Up poderia ser feito numa versão mínima se ele for necessário ao funcionamento do resto
Escalonamento final dos Use Cases no estudo de caso
Teremos que atacar Comprar Itens na primeira iteração além de uma versão reduzida de Start Up
Como Comprar Itens é complexo, pode-se quebrá-lo em várias versões incrementais:
Comprar Itens versão 1:
Pagamento em dinheiro apenas
Sem atualização de estoque
Loja stand-alone, sem integração a uma organização maior
Entrada manual de UPC, sem leitor de código de barras
Sem cálculo de impostos
3. Fase de Análise 1
Elaboração de um modelo conceitual
Modelo conceitual: adição de associações
Modelo conceitual: adição de atributos
Construção do glossário
Comportamento dinâmico: diagramas de sequência
Comportamento dinâmico: contratos
anal1 programa
Introdução
O modelo conceitual é o artefato mais importante criado durante a análise
O modelo conceitual ilustra os conceitos importantes do domínio do problema, suas
associações e atributos
Falaremos de associações e atributos nos próximos capítulos
Levantar um conjunto rico e expressivo de conceitos (objetos) durante a análise ajuda
muito a conduzir as fases de projeto e implementação
É importante lembrar que os conceitos levantados aqui são do domínio do problema e
não conceitos associados a software
Modelos conceituais
Ao fazer análise OO, a decomposição do domínio do problema utiliza objetos e não
funções ou processos como na análise estruturada
Exemplo de um modelo conceitual (com conceitos, associações e atributos)
Apenas a parte diagramática está sendo mostrada
UML permite associar descrições mais detalhadas dos conceitos, atributos, etc.
Exemplos errados
Especificação de produto
Especificações, projetos ou descrições de coisas
Descrição de um vôo
Loja
Lugares
Aeroporto
Venda, Pagamento
Transações (um momento notável)
Reserva
Caixa
Papeis de pessoas
Piloto
Loja, Prateleira
Coleções de outras coisas (containers)
Aeronave
Item
Coisas dentro das coleções
Passageiro
Fome
Conceitos abstratos
Acrofóbia (medo de altura)
Departamento de vendas
Organizações
Voamos Baixo, Ltda.
Política de devolução
Regras e políticas
Política de cancelamento
Catálogo de produtos
Catálogos
Catálogo de peças
Registros de assuntos financeiros, de trabalho, de contratos, Recibo, Plano de contas, Contrato de emprego
legais Log de manutenção
Linha de crédito
Instrumentos e serviços financeiros
Estoque
Manual do empregado
Manuais, livros
Manual de reparos
2. O caixa registra a identificação de cada item 3. Determina o preço do item e adiciona a informação ao total da
transação de venda
Se houver mais itens, o caixa pode informar a quantidade
também A descrição e preço do item corrente são exibidos
Alguns desses substantivos são conceitos (objetos), outros são atributos de conceitos
Falaremos mais sobre as diferenças em outro capítulo
Uma regra útil: Se pensarmos sobre um conceito X como número ou texto no mundo
real, ele pode ser um atributo; caso contrário, sempre será um conceito
Na dúvida, crie um conceito separado
Exemplo: Destino é um atributo de um vôo ou um conceito separado?
É um conceito separado (pensamos no destino como sendo um aeroporto)
Modelando descrições
Não se deve incluir como atributos descrições de conceitos a instâncias desses conceitos
por dois motivos
Repetição de informação
A descrição some se não houver instância
Exemplo: Não está correto incluir a descrição de um produto no conceito Produto
É melhor criar um novo conceito DescriçãoDeProduto que descreve instâncias de
Produtos
Objetivos
Identificar eventos e operações do sistema
Criar diagramas de sequência do sistema para Use Cases
Introdução
O modelo conceitual visto no capítulo anterior é um modelo estático
Agora, vamos considerar um modelo do comportamento dinâmico do sistema
Um diagrama de sequência do sistema ilustra eventos partindo de atores e estimulando
o sistema
Como ainda estamos investigando o que o sistema faz (e não como), tais diagramas
fazem parte do modelo de análise
Cuidado! Embora o autor do livro texto (Larman) indique que diagramas de sequência do
sistema são utilizados apenas na análise, eles podem ser utilizados no projeto também
Os diagramas de sequência do sistema são altamente dependentes dos Use Cases, pois
é a partir deles que criamos os diagramas
Trataremos o sistema como "caixa preta", isto é, investigando o que ele faz, mas não
como
Diagramas de sequência do sistema são elaborados para os Use Cases mais importantes
e as alternativas mais cruciais dos Use Cases
Cuidado! Não gere diagramas de sequência do sistema para situações óbvias ou de
fácil entendimento
Operações do sistema
Para cada evento, há uma operação correspondente que o sistema desempenha
Podemos enxergar o sistema como possuindo operações, necessárias para descrever um
comportamento dinâmico
No modelo conceitual, isso não era necessário pois estávamos descrevendo um
modelo estático
Exemplo:
Objetivos
Identificar associações num modelo conceitual
Diferenciar associações de conhecimento e associações de entendimento de modelo
Introdução
Queremos associar conceitos de forma a:
Satisfazer as necessidades de acesso a informação dos conceitos
Deixar o modelo conceitual mais fácil de entender
Associações
Uma associação é um relacionamento estrutural entre conceitos que indica uma conexão
interessante e útil
Outras associações que podem ser úteis são aquelas que melhoram o entendimento
do modelo conceitual
Gaveta-TPDV
A é uma parte física de B
Asa-Aeronave
TPDV-Loja, Item-Prateleira
A é fisicamente contido em B
Passageiro-Aeronave
Descrição de item-Catálogo
A é logicamente contido em B
Vôo-Schedule de vôo
Descrição de item-Item
A é uma descrição de B
Descrição de vôo-Vôo
Caixa-Loja
A é um membro de B
Piloto-Viação aérea
Departamento-Loja
A é uma subunidade organizacional de B
manutenção-Viação aérea
Caixa-TPDV
A usa ou gerencia B
Piloto-Aeronave
Cliente-Caixa
A se comunica com B
Agente de reserva-Passageiro
Cliente-Pagamento
A está relacionado com uma transação B
Passageiro-Ticket
Pagamento-Venda
A é uma transação relacionada com outra transação B
Reserva-Cancelamento
TPDV-TPDV
A é vizinho de B
Cidade-Cidade
TPDV-Loja
A pertence a B
Aeronave-Viação aérea
Nomes de associações
Use uma frase com verbo que facilite a leitura quando juntada com os conceitos em cada
extremidade
Exemplo: Loja Estoca Item
Associações são normalmente lidas da esquerda para a direita e de cima para baixo
Associações e implementação
Durante a análise associações não implicam em fluxos de dados ou informação de
navegabilidade
A implementação de uma associação será frequentemente uma referência entre dois
objetos
Porém, algumas associações da análise não serão implementadas
Novas associações que forem implementadas (na fase de implementação) devem ser
adicionadas ao modelo conceitual (foram esquecidas durante a modelagem conceitual)
TPDV-Loja
A é fisicamente contido em B
Item-Loja
A é um membro de B Caixa-Loja
Caixa-TPDV
A usa ou gerencia B Gerente-TPDV
Gerente-Caixa (mas provavelmente não aplicável no sistema)
Cliente-Pagamento
A está relacionado com uma transação B Caixa-Pagamento
Reserva-Cancelamento
A pertence a B TPDV-Loja
Pode-se debater cada associação para verificar a utilidade de ser incluída no modelo
Uma associação que implica conhecimento normalmente fica
As demais ficam se esclarecerem o modelo
Não há modelo único "correto"
Podemos argumentar que algumas associações do modelo poderiam sumir
Exemplos
Nenhuma das associações seguintes implica em conhecimento
Venda Entrada-por Caixa
Isso mudaria se precisássemos da identificação do caixa no recibo
TPDV Iniciado-por Gerente
Venda Iniciada-por Cliente
Loja Estoca Item
LinhaDetalheVenda Registra-Venda-de Item
anal1-2 programa anterior próxima
Objetivos
Identificar atributos num modelo conceitual
Distinguir entre atributos corretos e incorretos
Introdução
Precisamos identificar os atributos que servirão para satisfazer as necessidades de
informação dos Use Cases sob consideração na iteração corrente
Lembre que os atributos são do domínio do problema
Atributos
Um atributo é um valor de dado lógico de um objeto (ou instância de conceito)
Os atributos são identificados primariamente localizando a necessidade de lembrar
informação
Exemplo: Uma Venda tem atributos data e hora
Atributos válidos
Objetivos
Criar contratos para as operações do sistema
Introdução
Estamos continuando a investigar o comportamento dinâmico do sistema
Contratos descrevem os efeitos que as operações têm no sistema
Em UML, contratos são especificados usando pré-condições e pós-condições
Contratos são estabelecidos depois temos o modelo conceitual, que fizemos os
diagramas de sequência e que identificamos as operações no sistema
Continuamos com uma visão de caixa preta para o sistema (queremos caracterizar o que
o sistema faz e não como o faz)
Contratos
O diagrama de sequência não menciona a funcionalidade das operações
Isto é, o comportamento do sistema
Um contrato é um documento que descreve o que uma operação promete cumprir
As pré- e pós-condições descrevem as mudanças de estado do sistema
Contratos podem ser associados a operações do sistema como um todo ou a métodos
individuais (na fase de projeto)
Consideraremos apenas contratos de operações de sistema
Exemplo de um contrato
Tipo Sistema
Anotações
Se for uma nova venda, uma Venda foi criada (criação de instância)
Se for uma nova venda, a nova Venda foi associada ao TPDV (formação de associação)
Uma LinhaDetalheVenda foi criada (criação de instância)
Pós-condições A LinhaDetalheVenda foi associada à Venda (formação de associação)
LinhaDetalheVenda.quantidade recebeu o valor quantidade (mudança de atributo)
A LinhaDetalheVenda foi associada com uma EspecificaçãoProduto, baseado no casamento de
UPC (formação de associação)
Alguns comentários
O tipo pode ser Sistema, Classe de software ou Interface, dependendo do tipo de
contrato que se está elaborando
As anotações podem indicar dicar para a fase de projeto, algoritmos especiais
desejados, etc.
A saída indica informação que sai do sistema (mensagens, registros, ...), sem incluir
a interface com o usuário
As pré-condições mencionam as suposições sobre o estado do sistema antes da
execução da operação
Muitas dessas coisas poderão ser testadas durante a implementação
(programação defensiva)
As pós-condições mencionam as alterações ao estado do sistema como resultado da
execução da operação (isto, é depois que a operação terminou)
As pós-condições podem ser descobertas nas seguintes categorias:
Criação e destruição de instâncias
Modificação de atributos
Associações formadas e quebradas
Não mencione ações que são feitas na operação mas mudanças que ocorreram no
estado do sistema
Tire uma foto do sistema antes e depois da operação e descreva a diferença
entre as duas fotos
Descrevemos o que ocorreu e não como ocorreu
Outros contratos podem ser vistos no livro de Larman
Construção do Glossário
Ainda estamos em Análise
Objetivos
Expressar termos num glossário
Introdução
Um glossário é um documento simples que descreve termos
A definição de termos do domínio do problema é importantíssimo para manter limpa a
comunicação entre desenvolvedores e clientes
Para reduzir o risco de falahas de entendimento
Para ajudar a introduzir novos membros na equipe no meio do projeto
O glossário é elaborado em paralelo com outras fases e é elaborado ao longo da vida do projeto
Pode incluir definições de tipos, business rules, Use Cases, atributos, etc.
Eu particularmente não gosto de repetir informação aqui que esteja em outro lugar
Exemplo: descrição de atributos que poderão estar em outro documento
Serve como dicionário particular do projeto
4. Fase de Projeto 1
Da análise ao projeto
Projeto arquitetural
Descrição de use cases reais
Diagramas de colaboração
Padrões para atribuir responsabilidades
Projeto de soluções com objetos e padrões
Visibilidade
Diagramas de classe para a fase de projeto
Esquema de banco de dados e mapeamento OO-Relacional
proj1 programa
Da Análise ao Projeto
A análise se concentra na questão "O quê?"
Entendimento dos requisitos, conceitos e operações relacionados a um sistema
A próxima fase é a de Projeto, onde a questão "Como?" é abordada
O projeto de um sistema é dividido em duas partes
Projeto arquitetural (ou Projeto de Alto Nível)
Projeto detalhado (ou Projeto de Baixo Nível)
O projeto arquitetural lida com questões "macro"
O projeto detalhado lida com objetos individuais e suas interações
Procura-se resolver o problema de atribuir responsabilidades às classes
Também trata de resolver as questões de persistência de objetos
Padrões de Projeto (Design Patterns) são extremamente úteis durante o Projeto
detalhado
proj1-1 programa próxima
Projeto Arquitetural
Resumo da seção
Projeto de uma arquitetura
Decomposição do sistema: partições e camadas
Arquiteturas em n camadas
Uso de UML para modelar a arquitetura
Estruturas de controle
Opções de persistência
Reutilização: Frameworks e componentização
Resumo: Perguntas a fazer ao elaborar um projeto arquitetural
Arquiteturas em n camadas
Para sistemas de informação que incluam uma interface com o usuário e persistência
(isto é, quase todos!), usa-se frequentemente uma arquitetura em 3 camadas (3-tier
architecture)
Estruturas de controle
O paradigma de controle externo da aplicação deve ser escolhido
O paradigma diz como uma aplicação é controlada "de fora" e como interage com
seus usuários e outras aplicações
O controle interno dos objetos da aplicação não é visível fora da aplicação
Opções de persistência
Tratamos aqui apenas dos aspectos arquiteturais da persistência
Um capítulo à frente trata mais detalhadamente do mapeamento de projetos OO para
esquemas de BD relacionais
Três grandes pontos devem ser tratados com respeito à persistência de objetos:
Escolha da forma básica de persistência (na memória, em arquivos, usando um
SGBD)
Escolha do paradigma de SGBD (se usar um SGBD)
Determinação da estratégia de interação entre a aplicação e os dados
Arquivos de script
Para certas aplicações simples, basta executar um ou mais arquivos contendo
comandos de uma linguagem de consulta (SQL, OQL, ...)
Bom para certas situações tais como criar esquemas, popular um BD, prorotipagem
Comandos embutidos da linguagem de manipulação de dados de um SGBDOO
Neste caso, a aplicação acessa o BD diretamente
Parece natural devido ao casamento entre a linguagem hospedeira (digamos Java) e
um SGBDOO
Cuidado! Ao proceder assim, o acoplamento com o SGBDOO fica muito forte e pode
ser difícil mudar de SGBD posteriormente
Use o padrão ODMG para melhorar a portabilidade
Comandos SQL estáticos embutidos
Código SQl estático é conhecido em tempo de compilação
É relativamente simples de usar, mas:
Sem muitos cuidados (batching, etc.), pode resultar numa execução lenta
Acopla muito o código da aplicação ao esquema do BD
Resulta em código difícil de se ler
Em suma, tenta-se desta forma resolver o impedance mismatch com código estático
Implementar uma API customizada de acesso aos dados
Os acessos ao BD são encapsulados em poucos métodos da aplicação
Ajuda a manter a consistência dos dados
Ajuda a manter o escopo de transações
Ajuda a não contaminar a aplicação com um monte de código de acesso ao BD
Técnica muito usada
Métodos armazenados no BD
Stored procedures
Boa forma de reuso para várias aplicações
Certos cuidados ao usar Stored Procedures serão tratados num capítulo posterior
sobre persistência
Cuidado! Stored Procedures não são implementados de forma portável pelos vários
SGBDs do mercado
O padrão SQL não é obedecido pelos fabricantes
Linguagem de quarta geração
SGBDRs normalmente vêm com uma linguagem 4GL
Podem reduzir o esforço de desenvolvimento, mas não são portáveis
Uso de Middleware
Os serviços de persistência podem ser obtidos automaticamente usando Middleware
Enterprise JavaBeans
COM+
Vantagem: muitos serviços são obtidos além da persistência:
Gerência de transações distribuídas
Directory/Naming
Segurança
Tolerância a falha com Failover
Balanceamento de carga
Resource pooling
Monitoring, logging, ...
de design
Perguntas adicionais
Que ferramentas de desenvovimento serão geradas?
Como será o atendimento a outros requisitos (custo, mobilidade, uso de padrões, etc.)
Há considerações especiais de administração a levar em conta?
Como será a troca de versões?
Como será a distribuição do software?
Via Internet/Intranet?
Via mídia?
Pergunta final
Você já verificou que a arquitetura bolada pode atender a todos os requisitos
levantados?
proj1-2 programa anterior próxima
Use Cases reais são uma redefinição de Use cases essenciais mostrando o projeto do Use
case em termos de tecnologias específicas de entrada e saída
Exemplo: se uma GUI é usada, as telas com todos seus campos podem ser usadas para
detalhar o Use Case, mostrando as interações de baixo nível
Use Cases reais normalmente não são criados, só sendo necessários quando o cliente
exige uma alto grau de detalhamento da interface com o usuário
Normalmente, basta ter um esboço das telas com os detalhes sendo aprontados
durante a implementação
Exemplo
Use Case: Comprar item - versão 1 (pagamento à vista)
Atores: Cliente (iniciador), Caixa
Objetivo: Capturar uma venda e seu pagamento à vista
Um Cliente chega ao caixa de saída com itens sendo comprados. O Caixa registra os itens comprados e
Resumo:
recebe pagamento em espécie. No final, o Cliente sai com os itens.
Tipo: Primário e real
Referências cruzadas: Funções: R1.1, R1.2, R1.3, R1.7, R1.9, R2.1
quantidade é digitada no campo E. H é pressionado apõs a A descrição e preço do item corrente são exibidos nos campos B e
entrada de cada item F
6. ...
Diagramas de Colaboração
Introdução
Dois tipos de diagramas podem ser usados para mostrar as interações (mensagens)
entre objetos
Diagramas de Sequência
Diagramas de Colaboração
Os dois tipos de diagramas são chamados diagramas de interação
O objetivo é de mostrar como as pós-condições dos contratos serão realizadas
O diagrama de sequência é mais simples de usar quando se deseja mostrar apenas as
sequências de interações
O diagrama de colaboração é mais adequado quando se deseja expressar mais detalhes
da colaboração entre objetos
Exemplo de um diagrama de sequência:
No exemplo acima:
A mensagem façaPagamento é enviada a uma instância de uma TPDV
O TPDV envia a mensagem façaPagamento a uma instância de Venda
O objeto da classe Venda cria uma instância de um Pagamento
O Use Cases sugerem os eventos do sistema, os quais são mostrados explicitamente nos
diagramas de sequência do sistema
Uma primeira aproximação do efeito das operações do sistema é descrita nos contratos
As operações do sistema são mensagens que iniciam um diagrama de interação, o qual
ilustra como os objetos realizam a tarefa
Classe e instâncias
Links
Um link é uma conexão entre dois objetos
É uma instância de uma associação
Indica alguma forma de navegabilidade e visibilidade
Mensagens
Observe o número de sequência das mensagens
Parâmetros
O tipo do parâmetro é opcional
Valor de retorno
Sintaxe de mensagem
Pode-se usar a sintaxe UML (como acima) ou de outra linguagem (Java, por exemplo)
Mensagens a "this"
Iteração
A iteração é mostrada com um número de sequência e um *
A mensagem é enviada repetidamente
Criação de instâncias
A mensagem de criação independente de linguagem é "create"
O estereótipo «new» pode ser usado
Sequenciamento de mensagens
A primeira mensagem não é numerada
Mensagens condicionais
A mensagem só é enviada se o teste resultar em TRUE
Coleções
Um conjunto de instâncias (multiobjeto)
Introdução
Um sistema OO é composto de objetos que enviam mensagens uns para os outros
Uma mensagem é um método executado no contexto de um objeto
Escolher como distribuir as responsabilidades entre objetos (ou classes) é crucial para um bom projeto
Uma má distribuição leva a sistemas e componentes frágeis e difíceis de entender, manter, reusar e estender
Mostraremos alguns padrões de distribuição de responsabilidades
Padrões GRASP (General Responsibility Assignment Software Patterns)
São padrões de Larman
Ao mostrar padrões, apresentaremos princípios de um bom projeto OO
Veremos mais padrões de projeto em outros capítulos
Os padrões são utilizados enquanto se cria diagramas de interação
Responsabilidades
Responsabilidades são obrigações de um tipo ou de uma classe
Obrigações de fazer algo
Fazer algo a si mesmo
Iniciar ações em outros objetos
Controlar ou coordenar atividades em outros objetos
Obrigações de conhecer algo
Conhecer dados encapsulados
Conhecer objetos relacionados
Conhecer coisas que ele pode calcular
Exemplos
Uma Venda tem a responsabilidade de criar linha de detalhe (fazer algo)
Uma Venda tem a responsabilidade de saber sua data (conhecer algo)
Granularidade
Uma responsabilidade pode envolver um único método (ou poucos)
Exemplo: Criar uma linha de detalhe
Uma reponsabilidade pode envolver dezenas de classes e métodos
Exemplo: Responsabilidade de fornecer acesso a um BD
Uma responsabilidade não é igual a um método
Mas métodos são usados para implementar responsabilidades
O diagrama indica que objetos da classe Venda têm responsabilidade de criar linhas de detalhe de venda
Eles devem colaborar com objetos da classe LinhaDetalheVenda
Padrões
Uma definição informal:
"Cada padrão descreve um problema que ocorre frequentemente e então descreve o cerne da solução ao problema
de forma a poder reusar a solução milhões de vezes em situações diferentes"
Observe que o que é reutilizado são as classes e suas colaborações
Reuso de idéias, não código
Consistem de micro-arquiteturas de classes, objetos, suas responsabilidades e suas colaborações
Contêm o somatório da experiência dos melhores projetistas O-O!
Estão revolucionando o projeto de software desde 1995 quando o famoso livro da "Gang of Four" (GoF) apareceu com
o primeiro catálogo de 23 padrões
Ver bibliografia
Tem muito mais padrões aparecendo sempre
OOPSLA é uma grande fonte de padrões
Ficará mais claro com alguns exemplos
Design Patterns iniciaram a febre de padrões
Analysis Patterns
Testing Patterns
Business Patterns
Pedagogical Patterns
e mais ...
Os padrões GRASP são de Larman e são do tipo Design Patterns
Veremos padrões GRASP e GoF (em outro capítulo)
Um Nome
Descreve o problema de projeto, suas soluções e consequências em poucas palavras
Permite projetar num nível mais alto de abstração
Permite falar com outros sobre soluções e documentar código, já que os nomes de padrões estão ficando padronizados
"Todo mundo" conhece os 23 padrões da GoF
É equivalente a padronizar "lista encadeada", "pilha", etc. no mundo das estruturas de dados
Cuidado! Nem todo mundo conhece os padrões de Larman
O Problema
Descreve quando aplicar o padrão
Descreve o problema e o contexto
Pode descrever problemas específicos de projeto
Exemplo: como representar algoritmos como objetos?
Pode descrever estruturas de objetos ou de classes que são sintomas de um projeto inflexível
Às vezes, o padrão lista condições que devem se aplicar para usar o padrão
A Solução
Descreve os elementos constituintes do projeto, seus relacionamentos, responsabilidades e colaborações
A solução não descreve um projeto ou implementação concretos porque um padrão é um gabarito de solução para
várias situações
As Consequências
Os resultados e trade-offs da aplicação do padrão
Diz respeito a trade-offs de espaço, tempo, flexibilidade, extensibilidade, portabilidade
Expert
Problema
Qual é o princípio mais fundamental para atribuir responsabilidades?
Solução
Atribuir uma responsabilidade ao expert de informação - a classe que possui a informação necessária para preencher a
responsabilidade
Exemplo
No estudo de caso, alguma classe precisa saber o total de uma venda
Podemos dizer isso sobre a forma de uma responsabilidade:
Ainda não terminamos. Qual informação é necessária para determinar o subtotal para um item (uma linha de detalhe)?
Precisamos de LinhaDeVenda.quantidade e de EspecificaçãoDeProduto.preço
Quem é o information expert?
É a classe LinhaDeVenda
Chegamos aos seguintes diagramas:
Discussão
É o padrão mais usado de todos para atribuir responsabilidades
A informação necessária frequentemente está espalhada em vários objetos
Portanto, tem muitos experts parciais
Exemplo: determinar o total de uma venda requer a colaboração de 3 objetos, em 3 classes diferentes
Mensagens são usadas para estabelecer as colaborações
O resultado final é diferente do mundo real
No mundo real, uma venda não calcula seu próprio total
Isso seria feito por uma pessoa (se não houvesse software)
Mas no mundo de software, não queremos atribuir essa responsabilidade ao Caixa ou ao TPDV!
No mundo de software, coisas inertas (ou até conceitos como uma venda) podem ter responsabilidades: Tudo está
vivo!
Consequências
A encapsulação é mantida, já que objetos usam sua própria informação para cumprir suas responsabilidades
Leva a fraco acoplamento entre objetos e sistemas mais robustos e fáceis de manter
Leva a alta coesão, já que os objetos fazem tudo que é relacionado à sua própria informação
Creator
Problema
Quem deve criar novas instâncias de uma classe?
Solução
Atribuir à classe B a responsabilidade de criar instância da classe A se uma das seguintes condições se aplicar:
B agrega objetos da classe A
B contém objetos da classe A
B registra instâncias da classe A
B usa muito objetos da classe A
B possui os dados usados para inicializar A
B é um criador (creator) de objetos da classe A
Se mais de uma opção se aplica, escolha o B que agregue ou contenha objetos da classe A
Exemplo
No estudo de caso, quem deveria criar uma instância de LinhaDetalheVenda?
Pelo padrão Creator, precisamos achar alguém que agrega, contém, ... instâncias de LinhaDetalheVenda
Considere o modelo conceitual parcial abaixo:
Venda agrega instâncias de LinhaDetalheVenda e é portanto um bom candidato para criar as instâncias
Chegamos aos seguintes diagramas:
Discussão
Escolhemos um criador que deve estar conectado ao objeto criado, de qualquer forma, depois da criação
Isso leva a fraco acoplamento
Exemplo de criador que possui os valores de inicialização
Uma instância de Pagamento deve ser criada
A instância deve receber o total da venda
Quem tem essa informação? Venda
Venda é um bom candidato para criar objetos da classe Pagamento
Consequências
Fraco acoplamento, já que o objeto criado deve normalmente ser visível ao criador, depois da criação
Low Coupling
Problema
Como minimizar dependências e maximizar o reuso?
O acoplamento é uma medida de quão fortemente uma classe está conectada, possui conhecimento ou depende de
outra classe
Com fraco acoplamento, uma classe não é dependente de muitas outras classes
Com uma classe possuindo forte acoplamento, temos os seguintes problemas:
Mudanças a uma classe relacionada força mudanças locais à classe
A classe é mais difícil de entender isoladamente
A classe é mais difícil de ser reusada, já que depende da presença de outras classes
Solução
Atribuir responsabilidades de forma a minimizar o acoplamento
Exemplo
Considere o seguinte diagrama parcial de classes no estudo de caso
Supondo que a Venda deva ter conhecimento do pagamento (depois da criação) de qualquer jeito, a alternativa 2 tem
menos acoplamento (TPDV não está acoplado a Pagamento)
Dois padrões (Creator e Low Coupling) sugeriram diferentes soluções
Minimizar acoplamento ganha
Discussão
Minimizar acoplamento é um dos princípios de ouro do projeto OO
Acoplamento de manifesta de várias formas:
X tem um atributo que referencia uma instância de Y
X tem um método que referencia uma instância de Y
Pode ser parâmetro, variável local, objeto retornado pelo método
X é uma subclasse direta ou indireta de Y
X implementa a interface Y
A herança é um tipo de acoplamento particularmente forte
Uma seção futura esmiuça o assunto
Não se deve minimizar acoplamento criando alguns poucos objetos monstruosos (God classes)
Exemplo: todo o comportamento numa classe e outras classes usadas como depósitos passivos de informação
Tipos de acoplamentos (do menos ruim até o pior)
Acoplamento de dados
Acoplamento de controle
Acoplamento de dados globais
Acoplamento de dados internos
Acoplamento de dados
Situações
Saída de um objeto é entrada de outro
Uso de parâmetros para passar itens entre métodos
Ocorrência comum:
Objeto a passa objeto x para objeto b
Objeto x e b estão acoplados
Uma mudança na interface de x pode implicar em mudanças a b
Exemplo:
class Servidor {
public void mensagem(MeuTipo x) {
// código aqui
x.façaAlgo(Object dados); // dados e x estão acoplados
// (se interface de dados mudar x terá que mudar)
// mais código
}
}
Exemplo pior:
Objeto a passa objeto x para objeto b
x é um objeto composto ou agregado (contém outro(s) objeto(s))
Objeto b deve extrair objeto y de dentro de x
Há acoplamento entre b, x, representação interna de x, y
Exemplo: ordenação de registros de alunos por matrícula e nome
class Aluno {
String nome;
long matrícula;
// etc.
}
class ListaOrdenada {
Object[] elementosOrdenados = new Object[tamanhoAdequado];
class ListaOrdenada {
Object[] elementosOrdenados = new Object[tamanhoAdequado];
Interface Comparable {
public boolean lessThan(Object outro);
class ListaOrdenada {
Object[] elementosOrdenados = new Object[tamanhoAdequado];
class Lampada {
public static final ON = 0;
Ocorrência comum:
Objeto a manda mensagem para objeto b
b retorna informação de controle para a
Exemplo: retorno de código de erro
class Teste {
public int printFile(File toPrint) {
if(toPrint está corrompido ) {
return CORRUPTFLAG;
}
// etc. etc.
}
}
class Teste {
public int printFile(File toPrint) throws
PrintExeception {
if(toPrint está corrompido ) {
try {
Teste umTeste = new Teste();
umTeste.printFile(miniTeste);
} catch(PrintException printError) {
// faça algo
}
Consequências
Uma classefracamente acoplada não é afetada (ou pouco afetada) por mudanças em outras classes
Simples de entender isoladamente
Reuso mais fácil
High Coesion
Problema
Como gerenciar a complexidade?
A coesão mede quão relacionados ou focados estão as responsabilidades da classe
Também chamado coesão funcional (ver à frente)
Uma classe com baixa coesão faz muitas coisas não relacionadas e leva aos seguintes problemas:
Difícil de entender
Difícil de reusar
Difícil de manter
"Delicada": constantemente sendo afetada por outras mudanças
Uma classe com baixa coesão assumiu responsabilidades que pertencem a outras classes e deveriam ser delagadas
Solução
Atribuir responsabilidades que mantenham alta coesão
Exemplo
Mesmo exemplo usado para Low Coupling
Na primeira alternativa, TPDV assumiu uma responsabilidade de efetuar um pagamento (método façaPagamento())
Discussão
Alta coesão é outro princípio de ouro que deve ser sempre mantido em mente durante o projeto
class Angu {
public static int acharPadrão(String texto, String padrão) {
// ...
}
public static int média(Vector números) {
// ...
}
public static outputStream abreArquivo(string nomeArquivo) {
// ...
}
}
class Xpto extends Angu { // quer aproveitar código de Angu
...
}
Coesão lógica
Módulo faz um conjunto de funções relacionadas, uma das quais é escolhida através de um parâmetro ao chamar o
módulo
Semelhante a acoplamento de controle
Cura: quebrar em métodos diferentes
Coesão temporal
Elementos estão agrupados no mesmo módulo porque são processados no mesmo intervalo de tempo
Exemplos comuns:
Método de inicialização que provê valores defaults para um monte de coisas diferentes
Método de finalização que limpa as coisas antes de terminar
procedure inicializaDados() {
font = "times";
windowSize = "200,400";
xpto.nome = "desligado";
xpto.tamanho = 12;
xpto.localização = "/usr/local/lib/java";
}
class Xpto {
public Xpto() {
this.nome = "desligado";
this.tamanho = 12;
this.localização = "/usr/local/lib/java";
}
}
[Macintosh]
EquationWindow=146,171,406,661
SpacingWindow=0,0,0,0
[Spacing]
LineSpacing=150%
MatrixRowSpacing=150%
MatrixColSpacing=100%
SuperscriptHeight=45%
SubscriptDepth=25%
LimHeight=25%
LimDepth=100%
LimLineSpacing=100%
NumerHeight=35%
DenomDepth=100%
FractBarOver=1pt
FractBarThick=0.5pt
SubFractBarThick=0.25pt
FenceOver=1pt
SpacingFactor=100%
MinGap=8%
RadicalGap=2pt
EmbellGap=1.5pt
PrimeHeight=45%
[General]
Zoom=200
CustomZoom=150
ShowAll=0
Version=2.01
OptimalPrinter=1
MinRect=0
ForceOpen=0
ToolbarDocked=1
ToolbarShown=1
ToolbarDockPos=1
[Fonts]
Text=Times
Function=Times
Variable=Times,I
LCGreek=Symbol,I
UCGreek=Symbol
Symbol=Symbol
Vector=Times,B
Number=Times
[Sizes]
Full=12pt
Script=7pt
ScriptScript=5pt
Symbol=18pt
SubSymbol=12pt
Coesão procedural
Associa elementos de acordo com seus relacionamentos procedurais ou algorítmicos
Um módulo procedural depende muito da aplicação sendo tratada
Junto com a aplicação, o módulo parece razoável
Sem este contexto, o módulo parece estranho e muito difícil de entender
"O que está acontecendo aqui!!!????!!"
Não pode entender o módulo sem entender o programa e as condições que existem quando o módulo é chamado
Cura: reprojete o sistema
Coesão de comunicação
Todas as operações de um módulo operam no mesmo conjunto de dados e/ou produzem o mesmo tipo de dado de
saída
Cura: isole cada elemento num módulo separado
"Não deveria" ocorrer em sistemas OO usando polimorfismo (classes diferentes para fazer tratamentos diferentes
nos dados)
Coesão sequencial
A saída de um elemento de um módulo serve de entrada para o próximo elemento
Cura: decompor em módulos menores
Coesão funcional (a melhor)
Um módulo tem coesão funcional se as operações do módulo puderem ser descritas numa única frase de forma
coerente
Num sistema OO:
Cada operação na interface pública do objeto deve ser funcionalmente coesa
Cada objeto deve representar um único conceito coeso
Exemplo: um objeto que esconde algum conceito ou estrutura de dados ou recurso e onde todos os métodos são
relacionados por um conceito ou estrutura de dados ou recurso
Meyer chama isso de "information-strength module"
Consequências
Melhor claridade e facilidade de compreensão do projeto
Simplificação da manutenção
Frequentemente vai mão na mão com acoplamento fraco
Com granularidade baixa e funcionalidade bem focada, aumenta o reuso
Controller
Problema
Quem deveria receber a responsabilidade de tratar eventos do sistema?
Um evento do sistema é um evento de alto nível gerado por um ator externo
Estão associados a operações do sistema que já vimos nos Diagramas de Sequência do Sistema
Exemplo do estudo de caso: Caixa pressiona "Fim de venda"
Solução
Use um controlador
Um controlador é um objeto que não é de interface GUI responsável pelo tratamento de eventos do sistema
Um controlador define métodos para as operações do sistema
Atribuir a responsabilidade pelo tratamento de eventos do sistema a uma classe de acordo com uma das alternativas
abaixo:
Representa o "sistema" como um todo (facade controller)
Representa o negócio ou organização como um todo (facade controller)
Representa algo no mundo real que é ativo (por exemplo, o papel de uma pessoa) que poderia estar envolvido na
tarefa (role controller)
Representa um handler artificial de todos os eventos do sistema para um Use Case particular, normalmente
chamado "<NomeDoUseCase>Handler" (use case controller)
Exemplo
No estudo de caso, há várias operações de sistema:
Discussão
De forma geral, o mesmo controlador deve ser usado para todas as operações de um mesmo Use Case de forma a
manter a informação de estado do Use Case
A informação de estado pode ser útil para detectar sequências erradas de eventos de sistema
Exemplo: façaPagamento() antes de fimDeVenda()
Não coloque toda a inteligência no controlador
Delegue para outros objetos,para manter coesão
Use um Handler artificial quando as outras alternativas exibem acoplamento alto ou coesão baixa
Quando está surgindo um "God class"
Usado em sistemas mais complexos
Observe que classes "Window", "Applet", "Application", "View", "Document" não devem ser controladores
Tais classes podem receber o evento e delegá-lo ao controlador
Não se deve colocar business logic num objeto de interface com o usuário
Um design correto seria:
Consequências
Maior possibilidade de reuso, já que o business logic não está nos objetos de interface
Exemplo: embutir o business logic num objeto de interface não permitiria fazer EAI (Enterprise Application
Integration)
Ajuda a verificar o sequenciamento das operações do sistema, através do estado do controlador
São desenvolvidos em pequenos grupos em que cada pessoa assume o papel (Role) de uma ou mais classes
Mais detalhes aqui:
Designing Object-Oriented Software, Wirfs-Brock, Wilkerson e Wiener; Prentice Hall, 1990.
Algumas pessoas acham que é melhor usar ferramentas gráficas em vez de CRC
proj1-5 programa anterior próxima
Introdução
O aluno é responsável por estudar o capítulo 19 do livro-texto (Larman)
Trazer dúvidas para a aula
O capítulo utiliza padrões para atribuir responsabilidades e construir diagramas de
colaboração para os seguintes Use Cases do estudo de Caso
Comprar Itens, com os seguintes eventos de sistema:
entraItem
fimDeVenda
façaPagamento
Start Up, com o seguinte evento de sistema:
startUp
Os diagramas de colaboração são resumidos abaixo
Diagramas de Colaboração
entraItem
fimDeVenda
totalDeVenda
Não é operação de sistema, mas necessário para o cumprir o contrato
façaPagamento
Incluindo log da venda
troco
Não é operação de sistema, mas necessário para o cumprir o contrato
startUp
Criação do objeto inicial de domínio e demais objetos
Visibilidade
Introdução
Mensagens entre objetos só podem ocorrer se o receptor for visível ao remetente
Há quatro formas básicas de visibilidade
Visibilidade de atributos
Visibilidade de parâmetros
Visibilidade declarada localmente
Visibilidade global
Visibilidade de atributos
No estudo de caso, para que TPDV possa enviar uma mensagem a CatálogoDeProdutos,
usa-se um atributo em TPDV
Visibilidade de parâmetros
file://C:\_SYNC\_DISCIPLINAS\APOO\APOO Jacques UFCG\proj1\proj7.htm 04/02/2012
Visibilidade Page 2 of 3
Um objeto tem visibilidade para outro se este for recebido como parâmetro
No estudo de caso, para criar uma linha de detalhe de venda, TPDV chama
criaLinhaDetalhe de Venda, passando a especificação do produto
Assim, Venda tem visibilidade para a especificação de produto
Visibilidade global
Um objeto global é visível a todos
Não uma boa forma de ter visibilidade
Se for obrigado a ter objetos globais, é melhor usar o padrão de projeto Singleton (GoF)
Visibilidade em UML
Uso de Stereotypes
Não é usado em diagramas, normalmente
Visibilidade em Java
public para definir acesso a métodos públicos
Que fazem parte da interface ao objeto para quem vai chamá-lo
Não usa para atributos
private para acesso estritamente privado ao objeto (na realidade à classe)
protected para definir acesso a métodos ou atributos que devem ficar disponíveis para
quem estende a classe (acessível a subclasses)
"package" (sem modificador) para acesso quase público a todas as classes do package
Semelhante a friend de C++
usar com cuidado
proj1-7 programa anterior próxima
Introdução
Ao completar os diagramas de interação, pode-se completar o diagrama de classes
Na realidade, o diagrama de classes é criado em paralelo com os diagramas de interação
No final, falta incluir alguns detalhes finais
Se o diagrama de classes está sendo criado numa ferramenta CASE (ex. Rational
Rose), e a ferramenta será usada para gerar código, todos os detalhes de tipos
devem ser dados
Se o diagrama for preparado apenas para leitura por desenvolvedores, o nível de
detalhamento pode ser menor
O seguinte diagrama contém toda a informação de tipo
etc.
Exemplo:
Referências
Há uma discussão detalhada de como tratar persistência de objetos de várias formas nos livros:
Object-Oriented Modeling and Design for Database Applications, Blaha e Premerlani, Prentice Hall, 1998.
Database Design for Smarties, Muller, Morgan Kaufmann Publishers, 1999.
Este livro também cobre o mapeamento para SGBDs Objeto-Relacionais
Nossa discussão é um breve resumo do material desses dois livros
Um tratamento completo não pode ser dado nesta disciplina devido a restrições de tempo
Também tem uma breve discussão de como montar um framework de persistência no livro:
Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design, Larman, Prentice-
Hall, 1998.
Frameworks profissionais de persistência podem ser vistos nos produtos TopLink e CocoBase
Falaremos especialmente do mapeamento de um modelo OO para um esquema relacional
Não há tempo para tratar do mapeamento para SGBDs Objeto-Relacionais
Supõe-se uma certa familiaridade com o modelo relacional
Resumo da seção
Implementação da identidade de objetos
Implementação de domains
Implementação de classes
Implementação de operações
Implementação de associações simples
Implementação de associações avançadas
Implementação de herança simples
Implementação de herança múltipla
Resumo dos passos para o mapeamento OO-Relacional
Implementação de domains
Precisamos agora pensar sobre o tipo de dados que armazenaremos no BD
Que tipos serão usados nas declarações de colunas?
Domains são usados para lidar com a questão
Um domain é um subconjunto nomeado dos valores possíveis para um atributo
Domains:
Promovem um projeto mais consistente do que com o uso direto dos vários tipos de dados
Facilitam as mudanças
Ajudam a validar consultas
O padrão ANSI SQL-92 suporta o conceito de domain. Exemplo:
Pode-se assim criar domains para os tipos de dados que serão armazenados no BD
Infelizmente, a maioria dos SGBDs não dá suporte a domains!
Portanto, domains são geralmente implementados:
Usando cláusulas CHECK do SQL na definição dos atributos
Ou usando código de aplicação
Infelizmente, esse código de verificação de domains deve ser inserido sempre que se define um
atributo, já que não há suporte a domains ANSI
Para domains simples tais como nomes, Strings e valores financeiros:
Use o tipo de dado apropriado e especifique um tamanho
Use tipos ANSI (CHARACTER, CHARACTER VARYING, ...)
Ou use tipos do SGBD (Oracle: VARCHAR2, NUMBER, ...)
Para domains de enumeração, há 4 soluções
String de enumeração: armazene o atributo de enumeração como string
Use CHECK ou código de aplicação para verificar valores
Solução simples mas não pode ser estendida a muitos valores
Exemplo:
Uma tabela de enumeração: os valores possíveis para a enumeração são armazenados numa tabela à
parte
Pode haver uma tabela por enumeração ou uma tabela para todas as enumerações
O código de aplicação verifica que o valor do atributo pertence à tabela
Boa solução quando há muitos valores
Vantagem de ter os valores possíveis no próprio BD em vez de tê-los no código
Novos valores podem ser inseridos facilmente, sem modificar o código
Mas tabelas adicionais complicam o esquema e o código genérico de manipulação
Para pequenas enumerações, é mais fácil usar strings e um constraint em SQL
Use quando há muitos valores ou valores desconhecidos durante o desenvolvimento
Exemplo:
Implementação de classes
No modelo de projeto, algumas classes são persistentes e outras não
As classes persistentes têm o estereótipo UML «persistent»
Tratamos aqui apenas das classes persistentes
As demais definem um comportamento transiente que deve ser tratado no código da aplicação
Uma classe contém atributos e operações
Discutiremos o que fazer com as operações na próxima subseção
De forma geral, cada classe persistente se transforma numa tabela e cada atributo se transforma numa
coluna da tabela
Para cada tabela, trata-se a questão de identidade vista acima
Para cada atributo, trata-se a questão de domains vista acima
O modelo relacional não trata a visibilidade dos atributos
Todos os atributos são públicos
Para atributos que têm tag UML {OID}, use um constraint PRIMARY KEY
Para atributos que têm tag UML {alternate OID} (alternate key), use um constraint UNIQUE
Para atributos que têm tag UML {nullable}, não use o constraint NOT NULL, caso contrário, use NOT NULL
Alguns SGBDs permitem que você use o constraint NULL que não faz parte do padrão ANSI na definição
de colunas
Implementação de operações
Um SGBD Relacional não permite encapsular operações (métodos) com classes
Por outro lado, stored procedures são possíveis, o que permite colocar alguma funcionalidade dos métodos
dos objetos da aplicação no SGBD
Alguns SGBDs oferecem também stored functions que retornam valor
A questão básica é: "Que tipo de funcionalidade pode ser adequadamente colocada em stored procedures?"
No final das contas, podemos particionar o código entre o servidor de BD e as outras camadas (middle
tier, cliente)
O que é melhor colocar no SGBD e o que é melhor deixar fora?
Algumas regras sobre o que evitar seguem:
Não se pode colocar operações que lidam com aspectos transientes no SGBD
O código de stored procedures não pode acessar os objetos que estão em memória nas outras
camadas
Não se deve usar stored procedures para acessar dados atributo por atributo
Um SGBDR acessa dados naturalmente por registro, não por atributo individual
O acesso ao BD atributo por atributo resultaria numa aplicação muito lenta
Evite stored procedures que tenham que deixar "estado transiente" armazenado no BD para fazer outras
operações subsequentes (não armazenar estado comportamental usado entre chamadas)
Embora alguns SGBDs (Oracle, por exemplo) permitam que isso seja feito, é melhor que cada
chamada a um stored procedure seja reentrante e não dependa de estado
O sistema resultante é muito mais simples e fácil de manter e depurar
Tal tipo de estado deve ser mantido na camada do meio (aplicação)
Evite fazer grandes quantidades de verificação de erro, com condições de aborto, etc. no SGBD.
Mantenha isso em outra camada (aplicação ou cliente)
Algumas operações de alto desempenho poderão ser mais rápidas quando executadas numa linguagem
compilada (em outra camada), em vez de uma linguagem interpretada para executar stored procedures
(como PL/SQL do Oracle, Transact/SQL do SQLServer)
Evite fazer ações transacionais (COMMIT, ROLLBACK) em stored procedures
Faça isso em outra camada
A semântica de transação é expressa melhor na aplicação em si
Seria ruim um programador chamar um stored procedure e, depois, ao verificar um erro e tentar
Associações 1-para-1
Recomendado
Embutir a chave estrangeira em qualquer uma das classes
A chave estrangeira poderá ser nula se houver uma cardinalidade mínima de 0 no alvo
Alternativo
A associação pode ser implementada numa tabela à parte
A chave primária da tabela de associação pode ser qualquer uma das duas chaves estrangeiras
Vantagem: o esquema é mais extensível
Se houver mudança na cardinalidade da associação, basta mudar o constraint, não a estrutura de
tabelas
Desvantagens: fragmentação do esquema e maior overhead de navegação (com joins)
Desencorajado
Combinação: não junte duas classes e sua associação numa única tabela
O esquema é mais fácil de entender se seguir o modelo de objetos
Associações bi-direcionais: não coloque duas chaves estrangeiras, uma em cada tabela referenciando a
outra
Os SGBDs não vão manter a consistência das associações redundantes
Associações 0..1-para-1
Recomendado
Embutir a chave estrangeira na classe com a cardinalidade 0..1
A chave estrangeira é NOT NULL
Associações 1-para-muitos
Recomendado
Embutir a chave estrangeira na classe com cardinalidade "muitos"
Se a cardinalidade 1 (do alvo) puder ser 0-1, a chave estrangeira pode se nula
Exemplo:
Alternativo
A associação pode ser implementada numa tabela à parte
A chave estrangeira para a classe "muitos" é a chave primária da tabela de associação
Mesmas vantagens e desvantagens do mapeamento 1-para-1
Exemplo:
Associações muitos-para-muitos
Recomendado
A associação é mapeada para uma tabela distinta
A chave primária da tabela de associação é uma combinação das chaves estrangeiras
Exemplo:
Associação qualificada
Lembre que uma associação qualificada é uma associação "para-muitos" onde os objetos envolvidos do lado
"muitos" são total ou parcialmente especificados usando um atributo chamado o qualificador
Tais associações aumentam a precisão do modelo
Um qualificador seleciona um ou mais dentre vários objetos-alvo, com o efeito de reduzir a cardinalidade,
às vezes até 1
Quando a cardinalidade final é 1, a associação qualificada especifica uma forma precisa de selecionar o
objeto alvo a partir do objeto fonte
Exemplo:
Uma descrição de vôo se aplica a muitos vôos
A combinação de uma descrição de vôo mais uma data de partida especifica um vôo
Recomendado
O mapeamento é tal qual para a associação sem qualificador
No exemplo acima, uma chave estrangeira seria incluída na classe Vôo
Classe de associação
É uma associação com uma classe que a descreve
A classe de associação pode participar de outras associações
Recomendado
Implemente a associação com uma tabela distinta
A chave primária da classe de associação é uma combinação das chaves primárias das classes
envolvidas na associação
Exemplo:
Associação ordenada
Ocorre quando as ligações entre objetos possuem uma ordem
Exemplo: janelas podem estar ordenadas numa tela (indicando qual está na frente das outras)
O tag UML {ordered} é usado na associação ( ou {ordered by ...})
Recomendado
Introduzir um número de sequência como atributo
Associação ternária
Exemplo:
Recomendado
Uma tabela distinta deve ser usada para representar a associação ternária
Agregações
Recomendado
Agregações são associações e são implementadas como tal
Pode-se usar uma View para cada subclasse, no sentido de consolidar os dados herdados e facilitar o
acesso ao objeto
Exemplo:
Alternativo
Eliminação
Como otimização, subclasses que não contêm atributos, fora a chave estrangeira, podem ser
eliminadas
Desvantagens
O código de navegação tem que saber se há uma tabela ou não para implementar a subclasse
Falta de extensibilidade: se houver uma adição à subclasse, a tabela vai ter que voltar
Exemplo:
Neste caso, as subclasses são eliminadas e todos seus atributos migram para a tabela da
superclasse
Atributo desnecessários para certos objetos são nulos
Essa alternativa viola a segunda forma normal porque alguns atributos não dependem
completamente da chave primária
Exemplo:
Abordagem híbrida
Empurrar os atributos da superclasse para baixo mas manter a tabela da superclasse para navegar
nas subclasses
Exemplo:
Tabela de generalização
Semelhante ao mapeamento recomendado (tabelas para superclasse e subclasses) mas adicionando
uma tabela de generalização que amarra a chave primária da superclasse à chave primária da
subclasse
A chave primária das subclasses é a chave "natural" da tabela e não mais a mesma da
superclasse como no mapeamento recomendo inicial
5. Fase de Implementação
Mapeamento do projeto para código
Programa exemplo em Java
Testes de unidade
Adicionando uma interface com o usuário
impl programa
Criação de métodos
Feita a partir dos diagramas de colaboração, examinando as mensagens enviadas entre
os objetos
Tratamento de erros
Embora tenhamos nos concentrado na atribuição de responsabilidades, a atividade de
projeto deve também considerar como será feito o tratamento de erros
Normalmente usa o mecanismo de exceções
Uma parte significativa do código diz respeito ao tratamento de erros
Ordem de implementação
Classes individuais devem ser codificadas e sujeitas a testes de unidade na ordem da
menos acoplada à mais acoplada
Para minimizar scaffolding
Uma ordem sugerida no estudo de caso seria como mostrado abaixo
O código abaixo não inclui sincronização devido a acessos concorrentes, nem tratamento de persistência
O código abaixo é diferente do código do livro
Usa interfaces para desacoplar classes
Apenas as interfaces externas foram definidas de forma a produzir a documentação externa de quem usa as classes (ver
documentação externa)
Outras interfaces (internas) poderiam ser definidas para ter mais polimorfismo dentro do pacote
Inclui comentários javadoc para produzir documentação automaticamente
Alguns modificadores de acesso (public, ...) foram alterados
Usa Factory Methods (ver Design Patterns, em outro capítulo)
Alguns métodos faltam no livro
Exemplo: Loja tem que ter um getCatálogoDeProdutos(), senão a camada de interface com o usuário não consegue obter a
descrição de um produto com dado UPC para exibir ao cliente
Está em português
Acentuação é usada em atributos e métodos mas não em nomes de classes e interfaces porque, neste último caso, o nome do
arquivo é igual à classe/interface e quero evitar acentuação em nomes de arquivos (portabilidade)
Tem tratamento de erro completo com 3 tipos de exceções
Falta tratar mais um ou dois erros possíveis que estou deixando para o aluno descobrir (e tratar!)
O resultado é um código mais profissional
Porém, as coisas sempre podem ser melhoradas
Por exemplo: o uso de float para representar valores monetários não foi uma boa decisão do autor Larman
Por quê?
Como você faria?
Documentação
A documentação externa está aqui
Observe que nesta documentação, há apenas a informação que pode ser usada "de fora", isto é, pelas classes da interface do usuário
O único objeto que pode ser criado é uma Loja
A partir da Loja, pode-se obter um TPDV, ou um catálogo de produtos para fazer as outras operações associada à interface com o
usuário
A documentação interna está aqui
Útil para os desenvolvedores do pacote
Interface IPagamento
package tpdv;
/**
* Interface para qualquer tipo de pagamento.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
interface IPagamento {
/**
* Retorna o valor entregue pelo cliente para pagar a venda.
* @return O valor entregue pelo cliente para pagar a venda.
*/
float getValor();
}
Classe Pagamento
package tpdv;
/**
* Classe que representa um pagamento feito para uma venda.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
class Pagamento implements IPagamento {
/**
* O valor do pagamento de uma venda.
*/
private float valor;
/**
* Cria um pagamento.
* @param valorEntregue O valor entregue para pagar a venda.
*/
public Pagamento(float valorEntregue) {
this.valor = valorEntregue;
}
/**
* Cria um pagamento.
* @param valorEntregue O valor entregue para pagar a venda.
*/
public Pagamento(double valorEntregue) {
this.valor = (float)valorEntregue;
}
/**
* Retorna o valor entregue para pagar a venda.
* @return O valor entregue para pagar a venda.
*/
public float getValor() { return valor; }
}
Interface IEspecProduto
package tpdv;
/**
* Interface para qualquer tipo de especificação de produto.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public interface IEspecProduto {
/**
* Obtem o Universal Product Code (UPC) do produto.
* @return O Universal Product Code (UPC) do produto.
*/
int getUPC();
/**
* Obtem o preço do produto.
* @return O preço do produto.
*/
float getPreço();
/**
* Obtem a descrição do produto.
* @return A descrição do produto.
*/
String getDescrição();
}
Classe EspecificacaoDeProduto
package tpdv;
/**
* Classe que representa uma especificação de um produto do catálogo de produtos.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
class EspecificacaoDeProduto implements IEspecProduto {
/**
* O Universal Product Code (UPC) do produto.
*/
private int upc;
/**
* O preço do produto.
*/
private float preço;
/**
* A descrição do produto.
*/
private String descrição;
/**
* Cria uma especificação de produto.
* @param upc O Universal Product Code do produto.
* @param preço O preço do produto.
* @param descrição A descrição do produto.
*/
public EspecificacaoDeProduto(int upc, float preço, String descrição) {
this.upc = upc;
this.preço = preço;
this.descrição = descrição;
}
/**
* Cria uma especificação de produto.
* @param upc O Universal Product Code do produto.
* @param preço O preço do produto.
* @param descrição A descrição do produto.
*/
public EspecificacaoDeProduto(int upc, double preço, String descrição) {
this(upc, (float)preço, descrição);
}
/**
* Obtem o Universal Product Code do produto.
* @return O Universal Product Code do produto.
*/
public int getUPC() { return upc; }
/**
* Obtem o preço do produto.
* @return O preço do produto.
*/
public float getPreço() { return preço; }
/**
* Obtem a descrição do produto.
* @return A descrição do produto.
*/
public String getDescrição() { return descrição; }
}
Interface ICatalogoDeProdutos
package tpdv;
/**
* Interface para qualquer tipo de Catálogo de Produtos
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public interface ICatalogoDeProdutos {
/**
* Obtem a especificação de produto, dado o Universal Product Code (UPC)
* @param upc O Universal Product Code (UPC) do produto desejado
* @return A especificação de produto, dado o Universal Product Code (UPC)
*/
public IEspecProduto getEspecificação(int upc) throws ProdutoInexistenteException;
}
Classe CatalogoDeProdutos
package tpdv;
import java.util.*;
/**
* Classe que representa um catálogo de produtos.
* Um catálogo contém várias especificações de produtos.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
class CatalogoDeProdutos implements ICatalogoDeProdutos {
/**
* O catálogo de produtos é guardado aqui.
*/
private Map especsProdutos = new HashMap();
/**
* Cria um catálogo de produtos. Esta versão não trata de persistência.
* O catálogo é fixo e criado no construtor.
*/
public CatalogoDeProdutos() {
int upc1 = 100;
especsProdutos.put(new Integer(upc1),
makeEspecProduto(upc1, (float)1.99, "produto 1"));
int upc2 = 200;
especsProdutos.put(new Integer(upc2),
makeEspecProduto(upc2, (float)3.49, "produto 2"));
}
/**
* Obtém a especificação de um produto.
* @param upc o Universal Product Code do produto cuja especificação se deseja.
* @return A especificação do produto desejado.
*/
public IEspecProduto getEspecificação(int upc) throws ProdutoInexistenteException {
IEspecProduto espec = (IEspecProduto)especsProdutos.get(new Integer(upc));
if(espec == null) {
throw new ProdutoInexistenteException("Produto inexistente, UPC: " + upc);
}
return espec;
}
// factory method
protected IEspecProduto makeEspecProduto(int upc, float preço, String descrição) {
return new EspecificacaoDeProduto(upc, preço, descrição);
}
}
Classe ProdutoInexistenteException
package tpdv;
/**
* Exceção indicando produto inexistente no catálogo de produtos.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public class ProdutoInexistenteException extends TPDVException {
/**
* Cria uma exceção de produto inexistente
* @param mensagem Mensagem de erro imprimível
*/
public ProdutoInexistenteException(String mensagem) {
super(mensagem);
}
}
Interface ILinhaDetalhe
package tpdv;
/**
* Interface para qualquer tipo de linha de detalhe de uma venda.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
interface ILinhaDetalhe {
/**
* Retorna o subtotal da venda para os itens correspondendo a esta linha de detalhe.
* @return O subtotal da venda para os itens correspondendo a esta linha de detalhe.
*/
float subTotal();
}
Classe LinhaDetalheVenda
package tpdv;
/**
* Classe que representa uma linha de detalhe de uma venda.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
class LinhaDetalheVenda implements ILinhaDetalhe {
/**
* A quantidade de itens (do mesmo produto) neste detalhe de venda.
*/
private int quantidade;
/**
* A especificação do produto sendo comprado.
*/
private IEspecProduto espec;
/**
* Cria uma linha de detalhe de uma venda.
* @param espec A especificação do produto sendo comprado.
* @param quantidade A quantidade de itens (do mesmo produto) sendo comprados
*/
public LinhaDetalheVenda(IEspecProduto espec, int quantidade) {
this.espec = espec;
this.quantidade = quantidade;
}
/**
* Informa o subtotal da venda correspondendo a esta linha de detalhe.
* @return O subtotal da venda correspondendo a esta linha de detalhe.
*/
public float subTotal() {
return quantidade * espec.getPreço();
}
}
Interface IVenda
package tpdv;
/**
* Interface externa para qualquer tipo de venda.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public interface IVenda {
/**
* Retorna o troco da venda, após fazer um pagamento de uma venda.
* @return o troco da venda, após fazer um pagamento de uma venda.
*/
float getTroco();
/**
* Retorna o valor total da venda, até agora.
* @return o valor total da venda, até agora.
*/
float total();
}
Classe Venda
package tpdv;
import java.util.*;
/**
* Classe que representa uma venda de produtos feita através de um TPDV.
* Uma venda é composta de várias linhas de detalhe.
* Enquanto a venda não terminou, tais linhas de detalhe podem ser criadas.
* Um pagamento pode ser feita para pagar a venda.
* Pode-se calcular o troco a ser entregue ao cliente.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
class Venda implements IVenda {
/**
* As linhas de detalhe da venda.
*/
private List linhasDetalhe = new Vector();
/**
* A data da venda.
*/
private Date data = new Date(); // hoje
/**
* Indica se a venda terminou.
*/
private boolean isTerminada = false;
/**
* O pagamento efetuado para a venda.
*/
private IPagamento pagamento;
/**
* Calcula o valor total da venda.
* @return O valor total da venda.
*/
public float total() {
float total = (float)0.0;
Iterator it = linhasDetalhe.iterator();
while(it.hasNext()) {
total += ((ILinhaDetalhe)it.next()).subTotal();
}
return total;
}
/**
* Calcule o troco para a venda, após um pagamento.
* @return O troco para a venda.
*/
public float getTroco() {
return pagamento.getValor() - total();
}
/**
* Chamado para indicar que a venda terminou.
*/
void terminou() {
isTerminada = true;
}
/**
* Obtém o status da venda.
* @return true se a venda terminou; false caso contrário.
*/
boolean isTerminada() {
return isTerminada;
}
/**
* Cria uma linha de detalhe para a venda.
* @param espec A especificação do produto sendo comprado.
* @param quantidade A quantidade de itens (do mesmo produto) sendo comprados.
*/
void criaLinhaDetalhe(IEspecProduto espec, int quantidade) {
linhasDetalhe.add(makeLinhaDetalhe(espec, quantidade));
}
/**
// factory methods
protected ILinhaDetalhe makeLinhaDetalhe(IEspecProduto espec, int quantidade) {
return new LinhaDetalheVenda(espec, quantidade);
}
Classe NaoHaVendaException
package tpdv;
/**
* Exceção indicando operação necessitando de venda sem venda ativa
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public class NaoHaVendaException extends TPDVException {
/**
* Cria uma exceção de operação necessitando de venda sem venda ativa
* @param mensagem Mensagem de erro imprimível
*/
public NaoHaVendaException(String mensagem) {
super(mensagem);
}
}
Classe PagamentoInsuficienteException
package tpdv;
/**
* Exceção indicando pagamento insuficiente para uma venda.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public class PagamentoInsuficienteException extends TPDVException {
/**
* Cria uma exceção de pagamento insuficiente
* @param mensagem Mensagem de erro imprimível
*/
public PagamentoInsuficienteException(String mensagem) {
super(mensagem);
}
}
Interface ITPDV
package tpdv;
/**
* Interface para qualquer tipo de Terminal Ponto De Venda (TPDV).
* Um TPDV é usado para fazer uma venda (uma única venda de cada vez).
* Itens podem ser comprados até o final da venda.
* Um pagamento pode ser feito para a venda corrente.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public interface ITPDV {
/**
/**
* Chamado para indicar que a venda terminou.
*/
void fimDeVenda() throws NaoHaVendaException;
/**
* Chamado para adicionar à venda corrente um número de itens sendo comprados.
* @param upc O Universal Product Code do item sendo comprado.
* @param quantidade O número de itens sendo comprados.
*/
void entraItem(int upc, int quantidade) throws ProdutoInexistenteException;
/**
* Realiza um pagamento para uma venda.
* @param valorEntregue O valor entregue pelo cliente para pagar a venda.
*/
void façaPagamento(float valorEntregue) throws PagamentoInsuficienteException;
}
Classe TPDV
package tpdv;
/**
* Classe que implementa um Terminal Ponto De Venda (TPDV).
* Um TPDV é usado para fazer uma venda (uma única venda de cada vez).
* Itens podem ser comprados até o final da venda.
* Um pagamento pode ser feito para a venda corrente.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
class TPDV implements ITPDV {
/**
* O catálogo de produtos que podem ser vendidos neste TPDV.
*/
private ICatalogoDeProdutos catálogo;
/**
* A venda corrente sendo realizada no TPDV.
*/
private IVenda venda;
/**
* Cria um TPDV.
* @param catálogo Um catálogo de produtos que podem ser adquiridos neste TPDV.
*/
public TPDV(ICatalogoDeProdutos catálogo) {
this.catálogo = catálogo;
venda = null;
}
/**
* Obtém a venda corrente sendo realizada no TPDV.
* @return A venda corrente sendo realizada no TPDV.
*/
public IVenda getVenda() {
return venda;
}
/**
* Quando chamado, indica que a venda corrente sendo realizada no TPDV terminou.
*/
public void fimDeVenda() throws NaoHaVendaException {
if(venda == null) {
throw new NaoHaVendaException("Nao ha venda iniciada");
}
venda.terminou();
}
/**
* Informa um produto e a quantidade de itens deste produto sendo comprados na venda corrente.
/**
* Realiza um pagamento para a venda corrente do TPDV.
* @param valorEntregue O valor entregue pelo cliente para pagar a venda.
*/
public void façaPagamento(float valorEntregue) throws PagamentoInsuficienteException {
venda.façaPagamento(valorEntregue);
}
// factory method
protected IVenda makeVenda() { return new Venda(); }
}
Classe Loja
package tpdv;
/**
* Classe que implementa uma Loja. Cada loja tem um catálogo de produtos e um único TPDV.
*
* @author Craig Larman, Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.0
*/
public class Loja {
/**
* O catálogo de produtos da loja.
*/
private ICatalogoDeProdutos catálogo;
/**
* O terminal ponto de venda (TPDV) da loja.
* Uma loja só tem um único TPDV.
*/
private ITPDV tpdv;
/**
* Cria uma loja. O catálogo de produtos e o TPDV são automaticamente criados.
* @param valorEntregue O valor entregue para pagar a venda.
*/
public Loja() {
catálogo = makeCatálogo();
tpdv = makeTPDV(catálogo);
}
/**
* Obtem o TPDV da loja.
* @return O TPDV da loja.
*/
public ITPDV getTPDV() { return tpdv; }
/**
* Obtem o TPDV da loja.
* @return O TPDV da loja.
*/
public ICatalogoDeProdutos getCatálogoDeProdutos() { return catálogo; }
// factory methods
protected ICatalogoDeProdutos makeCatálogo() {
return new CatalogoDeProdutos();
}
Testes de Unidade
Justificativa
Reduzir defeitos (bugs) apresentados pelo produto
Cobrir uma gama maior de equipamentos e ambientes operacionais do que aqueles disponíveis na empresa
através de testes em clientes
Assegurar a qualidade final do produto para o cliente, incluindo avaliação de embalagem, manuais,
usabilidade, etc.
Estágios de testes
Testes são feitos em vários estágios
Fazer testes monolíticos do produto inteiro aumentaria o tempo necessário à depuração total do software
É melhor (mais rápido) testar partes menores para depois testar a integração dessas partes
Determinados testes devem ser feitos por pessoas fora da equipe de desenvolvimento no sentido de manter a
maior objetividade possível nessas fases de testes e no sentido de envolver também os usuários finais fora da
empresa
Automatização de testes
Os testes devem ser automatizados
Pelo menos nas fases em que isso é possível
Scripts de testes desenvolvidos ao longo do tempo aumentam o patrimônio da empresa e garantirão uma
qualidade cada vez maior
Tipos de testes
Testes de unidade
Para testar classes individuais
Feito pelo próprio programador da classe
Testa toda a interface da classe
Testes funcionais
Para testar os Use Cases
Feito pelo time de desenvolvimento
Testes de sistema
Uso de Total System Environment
Incluindo outros produtos de software, todas as plataformas, todas as configurações, etc.
Feito por uma equipe independente de testes
Testes de regressão
Antes de por o sistema na rua, mesmo que tenha havido apenas uma recompilação
Normalmente um subconjunto dos testes de sistema
Teste alfa
Teste de produto (com embalagem manual, etc.) num "cliente" dentro da empresa
Teste beta
Como teste alfa mas incluindo clientes fora da empresa
Testes de Unidade
Necessidade de automação
Clica um botão a qualquer momento para testar tudo!
Pelo menos um teste por dia (frequentemente vários testes por hora)
Uso de um framework de testes
Desenvolvimento e testes em paralelo
Absolutamente necessário ter testes de unidade, principalmente se fizer refactoring de código
Testar as classes da menos acoplada para a mais acoplada
Na realidade, segue a ordem de desenvolvimento
Desenvolvimento e testes de unidade feitos em paralelo!
Exemplo com o framework JUnit (teste da classe Venda)
Neste pacote, leia o artigo "Test Infected: Programmers Love Writing Tests"
Escrito por Beck (CRC, Extreme Programming) e Gamma (GoF)
Todos os testes estão aqui para o código do capítulo anterior
package tpdv;
import junit.framework.*;
import tpdv.*;
/**
* Testes da classe Venda.
*
*/
public class TestaVenda extends TestCase {
protected IVenda venda1;
protected IEspecProduto ep1;
protected IEspecProduto ep2;
protected IEspecProduto ep3;
}
try {
venda1.façaPagamento((float)20.0);
} catch(PagamentoInsuficienteException e) {
fail("facaPagamento(20.0) nao deveria gerar exception");
}
assertEquals("4", 20-1.99-(2*2.99)-(2*3.99), venda1.getTroco(), 0.001);
assert("3", !venda1.isTerminada());
venda1.terminou();
assert("3", venda1.isTerminada());
}
}
A classe principal
A classe TpdvFrame
6. Fase de Análise 2
Escolha de requisitos da segunda iteração
Relacionando múltiplos use cases
Extensão do modelo conceitual
Generalização
Organizando o modelo conceitual com packages
Refinamento do modelo conceitual
Modelo conceitual no estudo de caso
Comportamento do sistema: Diagramas de sequência e contratos na Segunda
Iteração
Comportamento do sistema: Diagramas de estado
anal2 programa
Introdução
Quando o modelo conceitual fica grande demais, pode ser quebrado em modelos
menores
Já falamos de camadas e partições em outro contexto (Projeto arquitetural), mas a
situação se aplica também no modelo conceitual
Packages em UML
Para referencia um conceito de um outro package, usa-se a notação
NomeDoPackage::NomeDoConceito
Introdução
UML tem uma notação especial para Use Cases em dois casos:
Use Cases grandes podem ser quebrados em vários Use Cases menores mas relacionados
A duplicação de certos passos de Use Cases diferentes pode se fatorada em um Use Case separado
Veremos como fazer isso para os Use Cases seguintes do estudo de caso:
Pagar com dinheiro
Pagar com cartão de crédito (CC)
Pagar com cheque
Na descrição do Use Case, a palavra "initiate" (iniciar) é usada para identificar que um Use Case usa outro
1. O Use Case inicia quando um cliente chega a um caixa munido de TPDV com itens a comprar
2. O caixa registra a identificação de cada item 3. Determina o preço do item e adiciona a informação ao total da transação de venda
Se houver mais itens, o caixa pode informar a quantidade também A descrição e preço do item corrente são exibidos
4. Ao completar a entrada dos itens, o caixa indica este fato ao TPDV 5. Calcula e apresenta o total da venda
Sequências Alternativas:
Seção 2: Identificador inválido de item informado: Exibit erro.
Seção 7: Cliente não pode pagar: Cancelar transação de venda
1. O Use Case inicia quando um cliente decide pagar uma venda com dinheiro, após descobrir o
valor total da venda
Sequências Alternativas:
Seção 2: O Cliente não tem dinheiro suficiente. Pode cancelar a venda ou iniciar
um novo método de pagamento
Seção 3: A gaveta não tem dinheiro para pagar o troco. Caixa pede dinheiro ao supervisor
ou pede ao Cliente para escolher outro método de pagamento
1. O Use Case inicia quando um cliente decide pagar uma venda com CC, após descobrir o valor
total da venda
2. O cliente entrega a informação de crédito para o pagamento 3. Gera um pedido de pagamento com CC e o envia a um Serviço de Autorização de Crédito
4. O Serviço de Autorização de Crédito autoriza o pagamento 5. Recebe uma aprovação de crédito do Serviço de Autorização de Crédito
Sequências Alternativas:
Seção 4: A autorização é negada pelo Serviço de Autorização de Crédito. O Caixa sugere
um outro método de pagamento.
1. O Use Case inicia quando um cliente decide pagar uma venda com cheque, após descobrir o
valor total da venda
3. O Caixa registra a informação de identificação e pede a autorização de pagamento com 4. Gera um pedido de pagamento com cheque e o envia a um Serviço de Autorização de
cheque Cheque
5. O Serviço de Autorização de Cheque autoriza o pagamento 6. Recebe uma aprovação de crédito do Serviço de Autorização de Cheque
Sequências Alternativas:
Seção 5: A autorização é negada pelo Serviço de Autorização de Cheque. O Caixa sugere
um outro método de pagamento.
Lugares
PagamentoComDinheiro, PagamentoComCC,
Transações (um momento notável)
PagamentoComCheque,
Papeis de pessoas
SistemaAutorizaçãoCC,
Outros sistemas externos a nosso sistema
SistemaAutorizaçãoCheque
Conceitos abstratos
SistemaAutorizaçãoCC,
Organizações
SistemaAutorizaçãoCheque
Eventos
Regras e políticas
Catálogos
Manuais, livros
Pode-se também identificar conceitos novos através dos substantivos nos Use Cases
Identificamos assim:
PedidoAutorizaçãoCC
RespostaAutorizaçãoCC
PedidoAutorizaçãoCheque
RespostaAutorizaçãoCheque
Modelo conceitual inicial resultante
Introdução
Apresentam-se idéias adicionais associadas à modelagem conceitual
Tipos Associativos
Quando uma Loja usa um Serviço de Autorização, a comunicação envolve uma
Identificação de Lojista
Onde seria armazenado o atributo?
Não pode ficar na Loja, pois a identificação é diferente para cada Serviço
Não pode ficar no Serviço, pois a identificação é diferente para cada Loja (Lojista)
Podemos modelar isso com um tipo associativo (ou Tipo de Associação)
Agregação e composição
A agregação é uma associação que modela um relacionamento todo-parte
O "todo" é chamado composite
As partes não têm nome padronizado (parte, componente, elemento, ...)
Exemplo: todo = mão, partes = dedos
Agregação na UML
Agregação composta
A cardinalidade do lado composite é 1
O losango é cheio (preto)
Significa que o composite possui as partes sozinho, sem compartilhamento
Agregação compartilhada
A cardinalidade do lado composite pode ser maior que 1
Significa que uma parte pode estar em mais de um composite
Exemplo no domínio de desenho gráfico
Associações qualificadas
Numa associação, um qualificador ajuda a determinar um ou mais objetos do lado
destino de uma associação
Exemplo: UPC é usado (conceitualmente) para identificar Especificações de Produtos
dentro de um Catálogo de Produtos
Observe que a cardinalidade foi afetada
Cuidado! Já que estamos na análise, uma associação qualificada apenas pode ajudar a
entender o modelo melhor
Nada significa sobre a escolha de chaves de recuperação no projeto
anal2-6 programa anterior próxima
Generalização
Generalização
Os conceitos PagamentoDinheiro, PagamentoCC e PagamentoCheque são semelhantes
de podem ser organizados num hierarquia de tipos chamada Generalização-
Especialização
Temos um supertipo (Pagamento) que representa um conceito mais genérico e subtipos
mais especializados
Duas regras úteis para verificar se subtipos são de fato subtipos de um supertipo
Regra 100%: 100% da definição do supertipo deve ser aplicáveis ao subtipo,
incluindo atributos e associações
Exemplo: Os subtipos de Pagamento devem:
Ter um atributo "valor"
Ter uma associação com Venda (Pagamento Paga-uma Venda)
Isso é verdade para os subtipos acima
Regra É-um: Todos os membros de um conjunto de subtipo pertence ao conjunto do
supertipo
Em linguagem natural, pode-se dizer Subtipo É-um Supertipo
O conceito subtipo representa uma coisa animada (animal, robô, ...) Biblioteca - não aplicável
que se comporta de forma diferente comparado ao subtipo ou outros Pesquisa de mercado - PessoaMasculina, subtipo de Pessoa, tem um
subtipos comportamento diferente de PessoaFeminina com respeito a hábitos
de compras
Tipos de Pagamentos
Ver a motivação da criação da hierarquia na figura abaixo
Tipos abstratos
Se um conceito não pode existir como instância concreta, estamos definindo um tipo
abstrato
Exemplo: um Pagamento não pode existir, sem que seja um dos subtipos
Em UML, um tipo abstrato recebe um nome em itálico
Mutação de tipos
Não modele mudanças de estado de objetos como mudanças de tipos (mutações)
Exemplo: um Pagamento pode ser não autorizado (no início) e passar a ser autorizado
Não modele isso como mudança de um tipo PagamentoNãoAutorizado para outro tipo
PagamentoAutorizado
Falaremos mais sobre mutações quando elaborarmos sobre herança num capítulo futuro
Generalização e herança
Não falamos de herança aqui porque estamos no mundo conceitual e não estamos
falando de classes de software
Em linguagens OO, uma subclasse de uma superclasse herda atributos e métodos
A herança é portanto uma forma de implementar a regra 100%
Ao passar para a fase de projeto, poderemos ou não usar herança para as hierarquias de
tipo
Exemplo, em C++, poderíamos usar uma única classe com templates em vez de uma
hierarquia de classes
anal2-4 programa anterior próxima
O Package Pagamentos
O Package Produtos
O Package Vendas
Contratos
Os contratos podem ser elaborados para cada evento do sistema
Diagramas de estado
Diagramas de estado podem ser usados para Use Cases complexos
Quando a ordem legal de eventos do sistema não é óbvia
Não Use Case complexo no estudo de caso, mas podemos mostrar um diagrama de
estado nesse contexto (Use Case Comprar Itens)
7. Fase de Projeto 2
Padrões adicionais para atribuir responsabilidades
Interfaces
Composição versus herança
Padrões de Projeto (Design Patterns)
O que são Design Patterns?
Elementos essenciais de um Design Pattern
Design Pattern: Factory Method
Design Pattern: Iterator
Design Pattern: Composite
Design Pattern: Strategy
Design Pattern: Decorator
Design Pattern: Template Method
Design Pattern: Observer
proj2 programa
Polimorfismo
O padrão de projeto Polimorfismo
Problema
Como tratar alternativas baseadas no tipo de objeto?
Se usar if-then-else ou switch-case, temos que mudar o código sempre que uma
nova alternativa surge
A modificação tem que ser feita em todo lugar onde a alternativa deve ser
tratada
O resultado é um código pouco extensível
Como criar componentes plugáveis?
Como trocar um componente por outro sem afetar os clientes do componente?
Há uma discussão mais detalhada de componentes em outro capítulo
Solução
Usar operações polimórficas ao comportamento que varia entre os tipos
Resultado: não se testa o tipo do objeto, chama-se a operação polimórfica,
simplesmente
Exemplo
No estudo de caso, quem deveria ser responsável pela autorização de diferentes tipos
de pagamentos?
Como a forma de obter autorização depende do tipo de pagamento (dinheiro, CC ou
cheque), podemos usar uma operação polimórfica autorize()
A implementação da operação será diferente em cada subtipo
Discussão
A idéia é semelhante ao padrão Expert ("eu mesmo faço")
Polimorfismo é um dos padrões mais importantes no projeto de software
Extensões são simples
Exemplo: como adicionar um método de pagamento com débito direto em conta
corrente?
Consequências
Adições futuras e não antecipadas têm pouco efeito no código existente
proj2-1 programa próxima
Interfaces
Herança de classe versus herança de interface
Há uma diferença grande entre uma classe e seu tipo
A classe define uma implementação
O tipo define apenas a interface oferecida para acessar objetos da classe
Um objeto pode ter muitos tipos
Classes diferentes podem ter o mesmo tipo
Herança de classe significa herança de implementação
A sub-classe herda a implementação da super-classe
É um mecanismo para compartilhar código e representação
Herança de interface (ou sub-tipos) descreve quando um objeto pode ser usado em vez de outro
Ao fazer herança de classe, automaticamente faz também herança de interface
Algumas linguagens não permitem definir tipos separadamente de classes
Mas, neste caso, a classe puramente abstrata serve
interface SelecionávelPorNome {
Iterator getIteradorPorNome(String nome);
}
interface Nomeável {
String getNome();
}
classe ComponenteDeSeleção {
Iterator it;
// observe o tipo do parâmetro (uma interface)
public ComponenteDeSeleção(SelecionávelPorNome coleção, String nome) {
it = coleção.getIteradorPorNome(nome); // chamada polimórfica
}
// ...
void geraListBox() {
response.out.println("<select name=\"nome\" size=\"1\">");
while(it.hasNext()) {
int i = 1;
// observe o tipo do objeto
Nomeável obj = (Nomeável)it.next();
response.out.println("<option value=\"escolha" + i + "\">" +
obj.getNome() + // chamada polimórfica
"</option>");
}
response.out.println("</select>");
}
}
Um exemplo de composição
Use composição para estender as responsabilidades pela delegação de trabalho a
outros objetos
Um exemplo no domínio de endereços
Uma empresa tem um endereço (digamos só um)
Uma empresa "tem" um endereço
Podemos deixar o objeto empresa responsável pelo objeto endereço e temos
agregação composta (composição)
Um exemplo de herança
Atributos, conexões a objetos e métodos comuns vão na superclasse (classe de
generalização)
Adicionamos mais dessas coisas nas subclasses (classes de especialização)
Três situações comuns para a herança (figura abaixo)
Uma transação é um momento notável ou intervalo de tempo
Benefícios da herança
Captura o que é comum e o isola daquilo que é diferente
A herança é vista diretamente no código
Problemas da herança
O encapsulamento entre classes e subclasses é fraco (acoplamento é forte)
Mudar uma superclasse pode afetar todas as subclasses
Isso viola um dos princípios básicos de projeto O-O (fraco acoplamento)
Às vezes um objeto precisa ser de uma classe diferente em momentos diferentes
Com herança, a estrutura está parafusada no código e não pode sofrer
alterações facilmente em tempo de execução
A herança é um relacionamento estático que não muda com tempo
Cenário: pessoas envolvidas na aviação (fugira abaixo)
O objeto "é um tipo especial de" e não "um papel assumido por"
O objeto nunca tem que mudar para outra classe
A subclasse estende a superclasse mas não faz override ou anulação de variáveis
e/ou métodos
Não é uma subclasse de uma classe "utilitária"
Não é uma boa idéia fazer isso porque herdar de, digamos, HashMap deixa a
classe vulnerável a mudanças futuras à classe HashMap
O objeto original não "é" uma HashMap (mas pode usá-la)
Não é uma boa idéia porque enfraquece a encapsulação
Clientes poderão supor que a classe é uma subclasse da classe utilitária e
não funcionarão se a classe eventualmente mudar sua superclasse
Exemplo: x usa y que é subclasse de vector
x usa y sabendo que é um Vector
Amanhã, y acaba sendo mudada para ser subclasse de HashMap
x se lasca!
Para classes do domínio do problema, a subclasse expressa tipos especiais de
papeis, transações ou dispositivos
Regra 1 (tipo especial): ok. Uma Reserva é um tipo especial de Transação e não
um papel assumido por uma Transação
Regra 2 (mutação): ok. Uma reserva sempre será uma Reserva, e nunca se
transforma em Compra (se houver uma compra da passagem, será outra
transação). Idem para Compra: sempre será uma Compra
Regra 3 (só estende): ok. Ambas as subclasses estendem Transação com novas
variáveis e métodos e não fazem override ou anulam coisas de Transação
Regra 4 (não estende classe utilitária): ok.
Regra 5 (tipo especial de papel/transação/dispositivo): ok. São tipos especiais de
Transação
proj2-3 programa
Design Patterns
O que são Design Patterns?
Uma definição informal:
"Cada padrão descreve um problema que ocorre frequentemente e então
descreve o cerne da solução ao problema de forma a poder reusar a solução
milhões de vezes em situações diferentes"
Observe que o que é reutilizado são as classes e suas colaborações
Reuso de idéias, não código
Consistem de micro-arquiteturas de classes, objetos, seus papeis e suas
colaborações
Contêm o somatório da experiência dos melhores projetistas O-O!
Estão revolucionando o projeto de software desde 1995 quando o famoso livro da
"Gang of Four" (GoF) apareceu com o primeiro catálogo de 23 padrões
Ver bibliografia
Tem muito mais padrões aparecendo sempre
OOPSLA é uma grande fonte de padrões
Ficará mais claro com alguns exemplos
Design Patterns iniciaram a febre de padrões
Analysis Patterns
Testing Patterns
Business Patterns
Pedagogical Patterns
e mais ...
Há um meta-padrão envolvido: Entre várias situações, isolar o que muda do que é
igual.
Uma sinopse dos Design Patterns da GoF está aqui.
pat-1 programa próxima
O Problema
Descreve quando aplicar o padrão
Descreve o problema e o contexto
Pode descrever problemas específicos de projeto
Exemplo: como representar algoritmos como objetos?
Pode descrever estruturas de objetos ou de classes que são sintomas de um
projeto inflexível
Às vezes, o padrão lista condições que devem se aplicar para usar o padrão
A Solução
Descreve os elementos constituintes do projeto, seus relacionamentos,
responsabilidades e colaborações
A solução não descreve um projeto ou implementação concretos porque um padrão
é um gabarito de solução para várias situações
As Consequências
Os resultados e trade-offs da aplicação do padrão
Diz respeito a trade-offs de espaço, tempo, flexibilidade, extensibilidade,
portabilidade
pat-2 programa anterior próxima
Factory Method
Introdução aos Padrões de Criação: Construindo Labirintos
GoF classifica os padrões em Padrões de Criação, Estruturais e de Comportamento
Padrões de criação abstraem o processo de instanciação de objetos
Usaremos a construção de labirintos para um jogo via computador para mostrar
alguns padrões de criação
Ignoraremos muitos detalhes do labirinto (o que pode estar no labirinto, os
jogadores, etc.)
Foco na criação dos labirintos
Um labirinto é um conjunto de salas
Uma sala conhece seus quatro vizinhos
Vizinhos podem ser outra sala, uma parede ou uma porta para outra sala
As classes importantes são Sala, Porta e Parede
Só trataremos as partes das classes que interessam para a criação do labirinto
O diagrama de classes segue abaixo (em UML)
Se precisar de um resumo de UML, ver aqui
Exemplo: se você estiver numa sala e quiser implementar a operação "vá para o
leste", o jogo determina qual ElementoDeLabirinto está do lado leste e chama entra
() deste objeto
O método entra() da subclasse específica determina o que ocorre
Num jogo real, entra() poderia aceitar o objeto jogador como parâmetro
Sala é a classe que implementa ElementoDeLabirinto e define as relações-chave
entre objetos
Mantém referências para 4 outros ElementoDeLabirinto
Armazena um número de sala para indentificar as salas do labirinto
new ElementoDeLabirinto[4];
private int númeroDaSala;
class Labirinto {
private Vector salas = new Vector();
public Labirinto() {
...
}
public void adicionaSala(Sala sala) {
...
}
public sala getSala(int númeroDaSala) {
...
}
}
class Jogo {
...
public Labirinto criaLabirinto() {
Labirinto umLabirinto = new Labirinto();
Sala sala1 = new Sala(1);
Sala sala2 = new Sala(2);
Porta aporta = new Porta(sala1,sala2);
umLabirinto.adicionaSala(sala1);
umLabirinto.adicionaSala(sala2);
return umLabirinto;
}
...
}
Veremos agora como mudar o projeto para criar diferentes tipos de labirintos
Labirintos encantados
Com portas travadas que precisam de um encantamento para abrir
Salas contendo encantamentos que podem ser apanhados
Labirintos perigosos
Salas com bombas que podem ser explodidas para danificar as paredes (e
talvez o jogador!)
Como mudar criaLabirinto() para facilmente criar estes novos tipos de labirintos?
O maior problema é que a solução atual nos força a colocar em código as
classes concretas que serão instanciadas
Usaremos padrões de criação para tornar o projeto mais flexível (mais reusável)
Resumo
A idéia é simples: em vez de um cliente que precisa de um objeto chamar new e
assim especificar a classe concreta que ele instancia, o cliente chama um método
abstrato (Factory Method) especificado em alguma classe abstrata (ou interface) e
a subclasse concreta vai decidir que tipo exato de objeto criar e retornar
Mudar a subclasse concreta que cria o objeto permite mudar a classe do objeto
criado sem que cliente saiba
Permite estender a funcionalidade através da construção de subclasses sem
afetar os clientes
Resumindo:
"Crie objetos numa operação separada de forma que subclasses possam fazer
override da forma de criação"
Estrutura genérica
Participantes
Produto: define a interface dos objetos criados pelo Factory Method
ProdutoConcreto: implementa a interface Produto
Criador: declara o Factory Method que retorna um objeto do tipo Produto
Às vezes, o Criador não é apenas uma interface mas pode envolver uma classe
concretaque tenha tem uma implementação default para o Factory Method para
retornar um objeto com algum tipo ProdutoConcreto default
Pode chamar o Factory Method para criar um produto do tipo Produto
CriadorConcreto: faz override do Factory Method para retornar uma instância de
ProdutoConcreto
Colaborações
Criador depende de suas subclasses para definir o Factory Method para que ele
retorne uma instância do ProdutoConcreto apropriado
Considerações de implementação
É boa prática usar uma convenção de nomes para alertar para o fato de que está
class Jogo {
// Factory Methods com default
public Labirinto criaLabirinto() {
return new Labirinto();
}
public Sala criaSala(int númeroDaSala) {
return new Sala(númeroDaSala);
}
public Parede criaParede() {
return new Parede();
}
public Porta criaPorta(Sala sala1, Sala sala2) {
return new Porta(sala1, sala2);
}
umLabirinto.adicionaSala(sala1);
umLabirinto.adicionaSala(sala2);
sala1.setVizinho(norte, criaParede());
sala1.setVizinho(leste, aporta);
sala1.setVizinho(sul, criaParede());
sala1.setVizinho(oeste, criaParede());
sala2.setVizinho(norte, criaParede());
sala2.setVizinho(leste, criaParede());
sala2.setVizinho(sul, criaParede());
sala2.setVizinho(oeste, aporta);
return umLabirinto;
}
Para criar um jogo perigoso, criamos uma subclasse de Jogo e redefinimos alguns
Factory Methods
// um novo CriadorConcreto
class JogoPerigoso extends Jogo {
public Parede criaParede() {
return new ParedeDestrutível();
}
public Sala criaSala(int númeroDaSala) {
return new SalaComBomba(númeroDaSala);
}
}
// um novo CriadorConcreto
class JogoEncantado extends Jogo {
public sala criaSala(int númeroDaSala) {
return new salaEncantada(númeroDaSala, jogaEncantamento());
}
public Porta criaPorta(Sala sala1, Sala sala2) {
return new portaPrecisandoDeEncantamento(sala1, sala2);
}
protected Encantamento jogaEncantamento() {
...
}
Iterator
Objetivo
Prover uma forma de sequencialmente acessar os elementos de um objeto
agragado sem expor sua representação interna
Motivação
Queremos isolar o uso de uma estrutura de dados de sua representação interna de
forma a poder mudar a estrutura sem afetar quem a usa
Para determinadas estruturas, pode haver formas diferentes de caminhamento
("traversal") e queremos encapsular a forma exata de caminhamento
Exemplo: árvore pode ser varrida "em ordem", "em pós-ordem", em "pré-
ordem"
Exemplo: podemos ter um "iterador com filtro" que só retorna certos elementos
da coleção
Exemplo: num editor de documentos, poderíamos ter os elementos do
documento organizados em árvores (documento consiste de páginas qe
consistem de parágrafos, ...) e ter um iterador especial para elementos não
gráficos (que podem ter sua grafia verificada, por exemplo)
A idéia do iterador é de retirar da coleção a responsabilidade de acessar e
caminhar na estrutura e colocar a responsabilidade num novo objeto separado
chamado um iterador
A interface Iterador:
Define uma interface para acessar os elementos da coleção
A classe Iterador
Implementa a interface Iterador
Mantém qualquer informação de estado necessária para saber até onde a
iteração (caminhamento) já foi
Como criar um iterador?
Não podemos usar new de uma classe concreta diretamente pois o iterador a
ser criado depende da coleção a ser varrids
Solução: a coleção tem um factory method para criar um iterador
Exemplo em java: Vector.iterator()
Estrutura
class Printer {
static void printAll(Iterator it) {
while(it.hasNext()) {
System.out.println(it.next().toString());
}
}
}
class LabirintoDeRatos {
public static void main(String[] args) {
Vector ratos = new Vector();
for(int i = 0; i < 3; i++ ) {
ratos.add(new Rato(i));
}
// iterador criado aqui
Printer.printAll(ratos.iterator());
}
}
Participantes
Iterador (Enumeration no Java original)
Define a interface para acessar e caminhar nos elementos
IteradorConcreto (elements() no Java original, iterator() no Java recente)
Implemementa a interface Iterator
Mantém estado para saber o elemento corrente na coleção
Coleção (não tem isso no Java antigo; no Java recente, chama-se Collection)
Define uma interface para criar um objeto iterador
ColeçãoConcreta (Vector, ArrayList, LinkedList, ... em Java)
Implementa a interface de criação do iterador e retorna uma instância do
IteradorConcreto
Consequências
file://C:\_SYNC\_DISCIPLINAS\APOO\APOO Jacques UFCG\pat\iterator.htm 04/02/2012
Iterator Page 3 of 4
Detalhes de implementação
Iteradores internos versus iteradores externos
Com iterador interno, o cliente passa uma operação a ser desempenhada pelo
iterador e este o aplica a cada elemento
Com iterador externo (mais flexíveis), o cliente usa a interface do iterador para
caminhar mas ele mesmo (o cliente) processa os elementos da coleção
Um iterador interno só é usado quando um iterador externo seria difícil de
implementar
Exemplo: para coleções complexas, manter o estado da iteração pode ser
difícil (teria que armazenar o caminho inteiro dentro de uma coleção
recursiva multi-nível). Neste caso, usar um iterador interno recursivo e
armazenar o estado na propria pilha de execução pode ser mais simples.
Tratamento de concorrência
O que ocorre se houver mudanças à coleção (novos objetos ou objetos
removidos) durante uma iteração (devido a um thread)?
Um "iterador fail-fast" indica imediatamente que está havendo acesso
concorrente (via exceção)
Alguns iteradores podem clonar a coleção para caminhar, mas isto é caro em
geral
Um "iterador robusto" permite fazer iterações e mudar a coleção sem se perder
Operadores do iterador
Pode permitir ou não andar para trás, pular posições, etc.
Iteradores nulos são interessantes para prover condições limites
Um iterador nulo sempre diz que a iteração acabou
Eexemplo: se todo objeto de um Composite (ver à frente) tiver um iterador,
uma folha poderia ter um iterador nulo
Exemplo de código
O iterador do Vector em Java seria semelhante ao que segue abaixo
Composite
Um problema a resolver: editor de documentos
Para introduzir este padrão (e alguns outros), usaremos o exemplo do projeto de
um editor de documentos WYSIWYG (What You See Is What You Get)
Semelhante a Word, por exemplo
Outros exemplos do padrão Composite no Swing de Java
O editor pode misturar texto e gráficos usando várias opções de formatação
A redor da área de edição estão os menus, scroll bars, barras de ferramentas, etc.
O primeiro problema de design que queremos atacar é como representar a
estrutura do documento
Essa estrutura afeta o resto da aplicação já que a edição, formatação, análise
textual, etc. deverão acessar a representação do documento
Um documento é um arranjo de elementos gráficos básicos
Caracteres, linhas, polígonos e outras figuras
O usuário normalmente não pensa em termos desses elementos gráficos mas em
termos de estruturas físicas
Linhas, colunas, figuras, tabelas e outras sub-estruturas
As sub-estruturas podem ser compostas de outras sub-estruturas, etc.
O usuário também pode pensar na estrutura lógica (frase, parágrafos, seções, etc.)
Não vamos considerar isso aqui mas a solução que usaremos se aplica a esta
situação também
A interface do usuário (UI) do editor deve permitir que o usuário manipule tais sub-
estruturas diretamente
Por exemplo, o usuário pode tratar um diagrama como uma unidade em vez de
uma coleção de elementos gráficos primitivos
O usuário deve manipular uma tabela como uma unidade e não como um
amontoado de texto e gráficos
Para permitir tal manipulação, usaremos uma representação interna que case com
a estrutura física do documento
Portanto, a representação interna deve suportar:
A manutenção da estrutura física do documento (o arranjo de texto e gráficos
em linhas, colunas, tabelas, ...)
A geração e apresentação visual do documento
Mapear posições da tela para elementos da representação interna. O editor vai
saber para o que o usuário está apontando
Tem algumas outras restrições no projeto
Texto e gráficos devem ser tratados uniformemente
A interface deve permitir embutir texto em gráficos e vice versa
Um gráfico não deve ser tratado como caso especial de texto, nem texto
como caso especial de gráfico, senão teremos mecanismos redundantes de
manipulação, formatação, etc.
A implementação não deve ter que diferenciar entre elementos únicos e grupos
de elementos na representação interna
O editor deve tratar elementos simples e complexos de forma uniforme
permitindo assim documentos arbitrariamente complexos
O décimo elemento da linha 5, coluna 2 pode ser um caractere único ou um
diagrama complexo com muitos elementos
Basta que o elemento saiba se desenhar, possa dar suas dimensões: ele
pode ter qualquer complexidade
Por outro lado, queremos analisar o texto para verificar a grafia, hifenizar, etc.
e não podemos verificar a grafia de gráficos ou hifenizá-las
Composição recursiva
Uma forma comum de representar informação estruturada hierarquicamente é
através da Composição Recursiva
Permite construir elementos complexos a partir de elementos simples
Aqui, a composição recursiva vai permitir compor um documento a partir de
elementos gráficos simples
Começamos formando linhas a partir de elementos gráficos simples (caracteres
e gráficos)
Múltiplas linhas formam colunas
Múltiplas colunas formam páginas
Múltiplas páginas formam documentos
etc.
Podemos representar essa estrutura física usando um objeto para cada elemento
Isso inclui elementos visíveis e elementos estruturais (linhas, colunas)
A estrutura de objetos seria como abaixo
Na prática, talvez um objeto não fosse usado para cada caractere por
razões de eficiência
Todos os elementos têm uma mesma interface para gerenciar os filhos (inserir,
remover, etc.)
Este padrão de projeto chama-se Composite e será discutido mais detalhadamente
agora
O Padrão Composite
Objetivo
Compor objetos em estruturas em árvore para representar hierárquias Parte-Todo
Composite permite que clientes tratem objetos individuais e composições
uniformemente
Participantes
Os nomes genéricos dados às classes abstratas são Component e Composite
Os nomes genéricos dados às classes concretas são Leaf e ConcreteComposite
Considerações de implementação
Adicionar referência ao pai de um objeto pode simplificar o caminhamento na
estrutura
Onde adicionar a referência ao pai? Normalmente é colocada na classe abstrata
Component
Exercício: colocar esta referência na figura acima
As subclasses herdam a referência e os métodos que a gerenciam
Compartilhamento de componentes
Útil para reduzir as necessidades de espaço
Por exemplo, caracteres iguais poderiam compartilhar objetos
Fazer isso complica se os componentes só puderem ter um único pai
O padrão "flyweight" mostra como resolver a questão
Maximização da interface de Component
Exercício para casa: certos livros colocam os métodos de gerenciamento de
filhos apenas na classe Composite porque uma folha não tem filhos!
Você concorda ou discorda com a maximização da interface de Component? Por
quê? Em outras palavras, é melhor ter uma interface idêntica para folhas e
Composite (transparência para o cliente) ou interfaces diferentes (segurança de
não fazer besteiras como adicionar um filho a uma folha, o que seria capturado
pelo compilador)?
Se mantivermos as interfaces de Component e Composite diferentes, como o
cliente pode testar se um objeto é folha ou composto?
Onde são armazenados os filhos?
Nós os colocamos em Composite mas eles poderiam ser colocados em
Component
A desvantagem é a perda de espaço para essa referência para folhas
Quando os filhos devem ter um ordem especial, deve-se cuidar deste aspecto
Usar um iterator é uma boa idéia
Cache de informação
As classes Composite podem manter em cache informação sobre seus filhos de
forma a eliminar (curto-circuitar) o caminhamento ou pesquisa nos filhos
Um exemplo: um Composite poderia manter em cache os limites do conjunto
de filhos de forma a não ter que recalcular isso sempre
Quando um filho muda, a cache deve ser invalidada
Neste caso, os filhos devem conhecer o pai para avisar da mudança
Como armazenar os filhos?
Vector, LinkedList, HashMap (qualquer coleção razoável)
interface ElementoDeDocumentoIF {
ElementoDeDocumentoIF getPai();
/**
* Retorna o font associado a este objeto.
* Se não houver font, retorna o font do pai.
* Se não houver pai, retorna null.
*/
Font getFont();
/**
* Associa um font a este objeto.
*/
void setFont(Font font);
/**
* Retorna o número de glyphs que este objeto contém.
*/
int getNumGlyph();
/**
* Retorna o filho deste objeto na posição dada.
*/
ElementoDeDocumentoIF getFilho(int índice)
throws ÉFolhaException;
/**
* Faça o ElementoDeDocumentoIF dado um filho deste objeto.
*/
void insereFilho(ElementoDeDocumentoIF filho)
throws ÉFolhaException;
/**
* remove o ElementoDeDocumentoIF dado
* da lista de filhos deste objeto.
*/
void removeFilho(ElementoDeDocumentoIF filho)
throws ÉFolhaException;
} // interface ElementoDeDocumentoIF
/**
* Faça o ElementoDeDocumentoIF dado
* um filho deste objeto.
*/
public void insereFilho(ElementoDeDocumentoIF filho)
throws ÉFolhaException {
throw new ÉFolhaException("Folha não tem filhos");
}
/**
* Remove o ElementoDeDocumentoIF dado
* da lista de filhos deste objeto.
*/
public void removeFilho(ElementoDeDocumentoIF filho) {
throws ÉFolhaException {
throw new ÉFolhaException("Folha não tem filhos");
}
} // class ElementoDeDocumento
/**
* Retorna o filho deste objeto na posição dada.
*/
public ElementoDeDocumentoIF getFilho(int índice) {
return (ElementoDeDocumentoIF)filhos.get(índice);
} // getFilho()
/**
* Faça o ElementoDeDocumentoIF dado
* um filho deste objeto.
*/
public synchronized void insereFilho(ElementoDeDocumentoIF filho) {
synchronized(filho) {
filhos.add(filho);
filho.pai = this;
avisoDeMudança();
} // synchronized
} // insereFilho()
/**
* Remove o ElementoDeDocumentoIF dado
* da lista de filhos deste objeto.
*/
public synchronized void removefilho(ElementoDeDocumentoIF filho) {
synchronized(filho) {
if(this == filho.pai) {
filho.pai = null;
}
filhos.remove(filho);
avisoDeMudança();
} // synchronized
} // removeFilho()
...
/**
* Uma chamada a esta função significa que um filho mudou,
* o que invalida a cache de informação que o objeto mantém
* sobre seus filhos.
*/
public void avisoDeMudança() {
cacheValida = false;
if(pai != null) {
pai.avisoDeMudança();
}
} // avisoDeMudança()
/**
* Retorna o número de glyphs que este objeto contém.
*/
public int getNumGlyph() {
if(cacheValida) {
return cacheNumGlyph;
}
cacheNumGlyph = 0;
for(int i = 0; i < filhos.size(); i++) {
ElementoDeDocumentoIF filho;
filho = (ElementoDeDocumentoIF)filhos.get(i);
cacheNumGlyph += filho.getNumGlyph();
} // for
cacheValida = true;
return cacheNumGlyph;
} // getNumGlyph()
} // class ElementoCompostoDeDocumento
Alguns comentários
getFont() usa a informação de seu pai
Um objeto composto usa uma cache para saber quantos glyphs compõem o
objeto
synchronized é usado ao mexer com dados que podem ser compartilhados para
possibilitar uma implementação multi-threaded
Strategy
Introdução
Vamos considerar o problema de formatação no editor de documentos WYSIWYG
Já sabemos como é a representação da informação
Mas ainda não sabemos como gerar um estrutura particular de linhas, colunas e
objetos simples para um documento particular
Isso é resultado da formatação do documento
Para simplificar, formatação significa apenas quebra em linhas
O resto da formatação pode ser tratado de forma análoga
Automatizar a formatação não é simples
Há um trade-off entre velocidade de formatação e a qualidade resultante
Muitas coisas devem ser consideradas tais como a "cor" de um documento (o
espalhamento uniforme de espaços em branco)
Muitos algoritmos têm sido propostos e podem ser usados no editor
O algoritmo pode até mudar em tempo de execução
Considere a formatação em word com "layout normal" (simples) e
"layout da página" (mais demorado mas mais WYSIWYG)
Ponto importante: queremos manter o algoritmo de formatação isolado da
estrutura do documento
Podemos adicionar elementos gráficos sem afetar o algoritmo de formatação
Podemos mudar o algoritmo de formatação sem afetar o tratamento de
elementos de documento
Isolaremos o algoritmo de formatação através de sua encapsulação num objeto
Usaremos uma hierarquia de classes para objetos que encapsulam algoritmos de
formatação
A raiz da hierarquia será uma classe com interface suficientemente genérica
para suportar uma larga gama de algoritmos
Cada subclasse implementa a interface para um algoritmo particular
O padrão Strategy
Objetivo
Definir uma família de algoritmos, encapsular cada um, e torná-los intercambiáveis
Strategy permite mudar os algoritmos independentemente dos clientes que os
usam
Estrutura genérica
Participantes
EstratégiaIF (exemplo: FormatadorIF)
Declara a interface comum a todos os algoritmos
O contexto usa essa interface para chamar o algoritmo definido em
EstratégiaConcreta
Estratégia (exemplo: Formatador)
Possível classe abstrata para fatorar código comum entre os algoritmos
EstratégiaConcreta (exemplo: FormatadorSimples)
Implementa o algoritmo usando a interface EstratégiaIF
Contexto (exemplo: ElementosAFormatar)
É configurado com um objeto EstratégiaConcreta
Mantém uma referência para um objeto EstratégiaIF
Pode definir uma interface para que a estratégia acesse seus dados
Decorator
Introdução
No editor de documentos WYSIWYG, vamos embelezar a interface do usuário (UI)
Vamos adicionar uma borda à área de edição
Vamos adicionar barras de rolagem (scroll bars)
Não vamos usar herança para adicionar este embelezamento
Não poderíamos mudar o embelezamento em tempo de execução
Para n tipos de embelezamento, precisaríamos de 2n-1 subclasses para ter
todas as combinações
Teremos mais flexibilidade se outros objetos da UI não souberem que está
havendo embelezamento!
Inclusão transparente
Usaremos composição em vez de herança evita os problemas mencionados, mas
quais objetos devem participar da composição?
O embelezamento em si será um objeto (digamos uma instância da classe Borda)
Isso nos dá dois objetos para fazer a composição: ElementoDeDocumento e Borda
Devemos decidir agora quem vai compor quem
A Borda pode conter o ElementoDeDocumento
Faz sentido já que a Borda engloba o ElementoDeDocumento na tela
O ElementoDeDocumento pode conter a Borda
Isso implica em mudar a classe ElementoDeDocumento para que ela conheça a
Borda
Usaremos a primeira escolha de forma a manter o código que trata bordas
inteiramente na classe Borda sem mexer nas outras classes
Como construir a classe Borda?
O fato da borda ter uma aparência na tela sugere que ela deveria ser uma
subclasse de ElementoDeDocumento
Tem um outro motivo mais forte ainda de fazer isso: clientes tratam objetos
ElementoDeDocumento e não deveriam saber se um ElementoDeDocumento
tem uma Borda ou não!
Clientes devem tratar ElementoDeDocumento uniformemente
Se um cliente manda um ElementoDeDocumento sem Borda se desenhar
não vai aparecer borda, caso contrário, vai aparecer borda, mas o cliente
não sabe
Isso implica que a Borda tem a mesma interface que ElementoDeDocumento
Fazemos Borda uma subclasse de ElementoDeDocumento para garantir este
relacionamento
Este conceito chama-se "Inclusão Transparente"
Composição com um único filho; e
Interfaces compatíveis
Clientes não sabem se estão tratando do objeto original (ElementoDeDocumento)
ou do seu incluidor (Borda)
O incluidor delega as operações para o objeto incluído, mas aumenta o
comportamento fazendo seu trabalho antes ou depois da delegação da operação
A classe MonoElementoDeDocumento
file://C:\_SYNC\_DISCIPLINAS\APOO\APOO Jacques UFCG\pat\decorator.htm 04/02/2012
Decorator Page 2 of 8
O padrão Decorator
Objetivo
Adicionar responsabilidades dinamicamente a um objeto
Decoradores provêem uma alternativa flexível à herança para estender
funcionalidade
file://C:\_SYNC\_DISCIPLINAS\APOO\APOO Jacques UFCG\pat\decorator.htm 04/02/2012
Decorator Page 5 of 8
Também chamado de
Wrapper
Estrutura genérica
Participantes
ComponenteIF (ex. ElementoDeDocumentoIF)
Considerações de implementação
A interface do decorador deve ser igual à interface do objeto sendo decorado
Se tiver que adicionar uma única responsabilidade, pode remover o decorador
abstrato
Neste caso, o decorador concreto pode tratar do forwarding para o Componente
incluso
Mantendo Componentes enxutos
A classe Componente deve ser mantida enxuta e os dados ser definidos nos
Componentes concretos
Caso contrário, os decoradores (que herdam de Componente) ficam "pesados"
import java.awt.*;
import java.applet.*;
Agora, considere um objeto A decorado pelo objeto B. Haja vista que o objeto B
decora o objeto A, o objeto B compartilha uma interface com o objeto A. Se algum
cliente recebe uma instância do objeto decorado (B) e tenta chamar um método
em B que não faça parte da interface de A, isto significa que o objeto B não é mais
um decorador, no sentido estrito do padrão? Além do mais, porque é importante
que a interface de um objeto decorador esteja de acordo com a interface do objeto
que ele decora?
pat-7 programa anterior próxima
Template Method
Um problema a resolver: login
Sua tarefa é de escrever uma classe para controlar o login de usuários numa
aplicação
Tem dois caminhos básicos para resolver o problema:
Escrever uma classe estanque para a aplicação sob consideração
Escrever uma solução genérica para qualquer aplicação
Para promover o reuso
A solução genérica seria um framework de login
Um esqueleto de login que seja comum a qualquer tarefa de login
Ganchos para permitir que cada aplicação customize o processo de login
Quais são os passos genéricos de login?
Prompt para o usuário fornecer sua identificação (ID) e senha
Autenticação da ID e senha
O resultado da autenticação deve ser um objeto
Este objeto pode encapsular qualquer informação que possa ser usada
adiante pela aplicação para comprovar a autenticação
Aviso visual de progresso indicando que a autenticação está sendo realizada
Um aviso de sucesso ao resto da aplicação que o login foi realizado e para
disponibilizar o objeto produzido pela autenticação
A lógica das etapas 1 e 3 não muda
A lógica das etapas 2 e 4 pode variar muito entre aplicações
Cada aplicação deverá prover seu próprio código
Solução
Criar uma classe abstrata Login contendo:
Um método principal (chamado Template Method) que capture a lógica
comum de login
Métodos abstratos para representar as etapas do algoritmo que devem ser
fornecidas pela aplicação particular
Estender a classe Login para prover implementações dos métodos abstratos
para uma aplicação particular (digamos para uma aplicação de
DecisionSupportSystem)
Resumo
Um Template Method define um algoritmo usando operações abstratas
Subclasses fazem override das operações para prover um comportamento concreto
Este padrão é a base para a construção de frameworks
Estrutura genérica
Participantes
ClasseAbstrata (Login)
Define operações abstratas que subclasses concretas definem para implementar
certas etapas do algoritmo
Implementa um Template Method definindo o esqueleto de um algoritmo
O Template Method chama várias operações, entre as quais as operações
abstratas da classe
ClasseConcreta (LoginDecisionSupportSystem)
Implementa as operações abstratas para desempenhar as etapas do algoritmo
que tenham comportamento específico a esta subclasse
Colaborações
ClasseConcreta depende de ClasseAbstrata para implementar as partes invariantes
do algoritmo
Considerações de implementação
É importante minimizar o número de operações abstratas que devem sofrer
override para completar o algoritmo
Motivo: simplificação para não chatear o programador
Convenções de nome
Métodos abstratos que devem sofrer override deveriam ter algo de comum no
nome
Exemplo: doXpto() // começa com "do"
Métodos-gancho que podem sofrer override deveriam ter algo de comum no
nome
Exemplo: logHook() // termina com "Hook"
Observer
Não vamos seguir a apresentação do livro GoF aqui, pois há críticas sobre a
solução dada
Falaremos das críticas à frente
Seguiremos a apresentação dada por Bill Venners em
http://www.javaworld.com/topicalindex/jw-ti-techniques.html (The 'event
generator' idiom)
Em particular, apresentaremos como este padrão é implementado em Java
Portanto, além de um Design Pattern (que não depende de linguagem),
apresentaremos um "Idioma Java" que mostra como implementar um Design
Pattern numa linguagem particular
Objetivo
O padrão Observer permite que objetos interessados sejam avisados da mudança
de estado ou outros eventos ocorrendo num outro objeto
O objeto sendo observado é chamado de:
"Subject" (GoF)
"Observable" (java.util)
"Source" ou "Event Source" (java.swing e java.beans)
Provedor de informação (Bill Venners)
Gerador de eventos (Bill Venners)
O objeto que observa é chamado de
Observer (GoF e java.util)
Listener (java.swing)
Java usa este padrão em 2 lugares mas de formas diferentes!
A forma java.util não é boa (ver críticas adiante)
Usaremos as palavras Source e Listener
Também chamado de
Event Generator, Dependents, Publisher-Subscriber
Exemplo
Como projetar um sistema que modele um telefone e todos os objetos que
poderiam estar interessados quando ele toca?
Os objetos interessados poderiam ser:
Pessoas que estejam perto (na mesma sala)
Uma secretária eletrônica
Um FAX
Até um dispositivo de escuta clandestina :-)
Os objetos interessados podem mudar dinamicamente
Pessoas entram e saem da sala onde o telefone está
Secretárias eletrônicas, FAX, etc. podem ser adicionados ou removidos durante
a execução do programa
Novos dispositivos poderão ser inventados e adicionados em versões futuras do
programa
Qual é a solução básica de projeto?
Faça do telefone um Event Source
file://C:\_SYNC\_DISCIPLINAS\APOO\APOO Jacques UFCG\pat\observer.htm 04/02/2012
Observer Page 2 of 8
O problema
Em Java, um objeto (o Source) envia informação para outro objeto (o Listener)
pela chamada de um método do Listener
Mas, para que isso seja possível:
O Source deve ter uma referência ao Listener
O tipo desta referência deve ser uma classe ou interface que declare ou herde o
método a chamar
Fazer com que o tipo da referência seja a classe (concreta) do Listener não
funciona bem, porque:
O número e tipos dos Listeners não é conhecido em tempo de compilação
Os vários listeners poderão não fazer parte de uma mesma hierarquia de
objetos
Não queremos criar um acoplamento forte entre Source e Listeners
A solução vai se basear primordialmente em interfaces para resolver o problema
Aliás, este é um excelente exemplo do poder de interfaces para prover
polimorfismo envolvendo classes não relacionadas por herança (de
implementação)
Para ajudar o programador, é comum, em Java, criar uma classe "adapter" que
implemente todos os métodos de uma interface com métodos que nada fazem
As classes que devem implementar a interface podem herdar do adapter e
fazer override de alguns poucos métodos
Para cada interface de Listener que contenha mais do que um método, defina uma
classe adapter que implemente a interface por inteiro com métodos que nada
fazem
Dê um nome à classe substituindo Listener com Adapter
Exemplo, para a interface TelefoneListener, a classe seria TelefoneAdapter
Estrutura
Exemplo de código
No arquivo TelefoneEvent.java:
void telefoneTocou(TelefoneEvent e) {
}
void telefoneAtendido(TelefoneEvent e) {
}
}
import java.util.*;
if (telefoneListeners.contains(l)) {
return;
}
telefoneListeners.add(l);
}
telefoneListeners.remove(l);
}
No arquivo Pessoa.java
fone.addTelefoneListener(se);
fulano.escutaTelefone(fone);
Considerações de implementação
Um Listener pode estar cadastrado junto a vários objetos Source
Ele pode descobrir quem o esta notificando se o objeto evento contiver uma
referência ao source (como temos no idioma Java)
Quem dispara o evento original?
Se cada método que muda o estado do Source disparar um evento, pode haver
eventos demais se houver mudanças de estado demais
Neste caso, pode-se deixar um método público do Source que clientes ativam
para disparar um evento depois que todas as mudanças ao estado forem feitas
O problema é que o cliente pode "esquecer" de chamar este método
Assegurar a consistência do estado do objeto antes de disparar o evento
Particularmente perigoso se um método do Source fizer:
8. Tópicos Avançados
Refactoring
Extreme Programming
Frameworks
Componentes
etc programa
Refactoring
Introdução
Entropia de software
Programas começam num estado bonito, bem projetados mas, com tempo, as
sucessivas adições e mudanças fazem o programa perder sua estrutura
Resultado final: espaguete
Motivos
Programa muda em formas não previstas
Programa não é perfeitamente entendido (mesmo sendo nosso!) e o enxerto é
menos perfeito do que poderia ser
Pressões de tempo
Seria melhor reprojetar o sistema para limpá-lo, mas preferimos empurrar com
a barriga
Tem um excelente livro de Fowler sobre Refactoring
Refactoring
Refactoring é um termo usado para descrever técnicas que diminuem a dor de
reprojetar
Mudanças feitas a um programa que alteram apenas sua organização, não sua
funcionalidade
Transformações que preservam o comportamento
Extreme Programming
Alegação básica: software é difícil demais para gastar tempo com coisas que não
são importantes
Então, o que é importante?
Codificar: se, no final do dia, o programa não funciona e não ajuda o cliente a
ganhar dinheiro, você não fez nada
Testar: você tem que saber quando terminou. Os testes dizem isso. Se você for
inteligente, vai escrevê-los primeiro para saber instantaneamente quando você
terminou
Escutar: tem que saber qual é o problema a resolver. Escute clientes, gerentes
e pessoas do negócio
Refatorar: você tem que escutar o que o programa está dizendo para você
sobre sua própria estrutura e colocar este conhecimento de volta no programa
Se alguém falar para você que fazer software é outra coisa (além de codificar,
testar, escutar, refatorar), ele está tentando lhe vender algo
Não falei que eram irreverentes?!
Por isso, inventaram uma disciplina de desenvolvimento de software (xp) baseada
nos seguintes valores:
Simplicidade
Comunicação
Feedback
Agressividade
As práticas de XP foram definidas para balancear as forças agindo no projeto
Práticas de XP
Para terminar esta introdução, eis a lista de práticas de XP (leia cada uma dessas
práticas no wiki e resuma cada uma em uma frase nas suas próprias palavras)
DoTheSimplestThingThatCouldPossiblyWork encoraja não sobre-projetar (nem sub-
projetar)
AskTheCode porque ele sabe (... dizer que precisa ser refatorado, ... dizer onde
está o bug, ...)
UnitTests: essa turma testa, testa, testa ... para não quebrar o código um do
outro. Unit testes são escritos para cada classe e testados usando o
TestingFramework. Testes de unidade pertencem ao desenvolvedor.
FunctionalTests para testar as estórias e saber se as necessidades do usuário estão
sendo atingidas. Os usuários forneceram os dados usados nestes testes. Testes
funcionais pertencem ao cliente.
ContinuousIntegration gerando um processo iterativo para evitar IntegrationHell. A
integração de uma nova versão de algo (mesmo pequeno) é quase instantânea,
logo depois dos testes.
ContinuousIntegrationRelentlessTesting (como acima, mas lembrando que tem que
testar, testar e testar)
RefactorMercilessly para manter o código limpo e o progresso rápido (ver também
WikiPagesAboutRefactoring). Só funciona bem se tiver muitos testes para se
assegurar de que não quebrou nada.
Recomenda-se chegar à ExtremeNormalForm
"Your classes are small and your methods are small; you've said everything
OnceAndOnlyOnce and you've removed the last piece of unnecessary code.
Outside a sprinter shakes out her legs and settles into the block, waiting for the
crack of the gun while a cool wind arches across the track.