View
215
Download
0
Category
Preview:
Citation preview
1
UNIVERSIDADE FEDERAL DE SANTA CATARINA Departamento de Informática e Estatística
Ciências da Computação
MAGIC: Um framework para jogos de cartas
por
André Luís Knabben Thiago Robert
Professor Doutor Ricardo Pereira e Silva Orientador
Florianópolis, 06 de setembro de 2002.
2
UNIVERSIDADE FEDERAL DE SANTA CATARINA Departamento de Informática e Estatística
Ciências da Computação
MAGIC: Um framework para jogos de cartas
por
André Luís Knabben Thiago Robert
Professor Doutor Ricardo Pereira e Silva Orientador
3
MAGIC: Um framework para jogos de cartas
Professor Doutor Ricardo Pereira e Silva Orientador
Professor Rosvelter Coelho Membro da Banca Avaliadora
Professor Vitório Mazzola Membro da Banca Avaliadora
4
Resumo
Desenvolver aplicações utilizando o paradigma de orientação a objetos é uma tarefa
complexa. Entretanto, projetar artefatos de software visando a reusabilidade pode ser ainda
mais difícil. Frameworks e padrões de projeto estão entre os artefatos de software
reusáveis utilizados dentro do paradigma OO.
Um framework é um artefato de software reusável de alta granularidade que
generaliza um determinado domínio de aplicação, ou seja, cobre as características comuns
às aplicações de um domínio. É composto de classes abstratas e concretas e provê um
modelo de interação ou colaboração para as instâncias de suas classes.
Padrão de projeto é a descrição de uma solução para um problema de projeto que
ocorre constantemente. Padrões de projeto ajudam na escolha de alternativas para tornar
um sistema reusável, podendo até melhorar a documentação e manutenção de sistemas
existentes fornecendo uma especificação explicita da interação entre classes e objetos e o
objetivo dessa interação.
O presente documento consiste na descrição do desenvolvimento de um framework
OO generalizando o domínio dos jogos de carta. Padrões de projeto foram utilizados
durante o desenvolvimento do framework e, por isso, também são abordados.
5
Abstract
Developing object oriented applications is a complex task. Designing reusable
software artifacts is even more complex. Frameworks and design patterns are among the
most popular reusable software artifacts in the OO paradigm.
A framework is a reusable software artifact that generalizes a given domain of
applications, i.e., it covers the common characteristics of every application in the domain.
A framework is composed of abstract and concrete classes and provides an interaction
model for the instances of its classes.
A design pattern describes the core of the solution to a problem which occurs over
and over again. Design patterns help in the selection of design alternatives that make a
system reusable and avoid alternatives that compromise reusability. Design patterns can
even improve the documentation and maintenance of existing systems by furnishing an
explicit specification of class and object interactions and their underlying intent.
The present paper consists in the description of the development of a card games OO
framework, using design patterns were used during the framework development and,
therefore, will be treated as well.
6
Sumário
1. INTRODUÇÃO.............................................................................................10
1.1 Tema ................................................................................................................................................... 10
1.2 Delimitação do Tema...................................................................................................................... 10
1.3 Objetivo Geral .................................................................................................................................. 10
1.4 Objetivos Específicos .................................................................................................................... 10
1.5 Motivação .......................................................................................................................................... 11
2. FRAMEWORKS ORIENTADOS A OBJETOS ............................................12
2.1 Conceitos Básicos.......................................................................................................................... 12
2.2 Vantagens ......................................................................................................................................... 13 Modularidade............................................................................................................................................... 13 Reusabilidade............................................................................................................................................... 13 Extensibilidade ............................................................................................................................................ 14
2.3 Desafios............................................................................................................................................. 14 Esforço de desenvolvimento ........................................................................................................................ 14 Curva de Aprendizado ................................................................................................................................. 15 Integração .................................................................................................................................................... 15 Manutenção.................................................................................................................................................. 15 Validação ..................................................................................................................................................... 15 Eficiência ..................................................................................................................................................... 15 Falta de Padrões ........................................................................................................................................... 15
2.4 Desenvolvimento de Frameworks .............................................................................................. 16 Análise do Domínio ..................................................................................................................................... 16 Modelagem .................................................................................................................................................. 16 Implementação............................................................................................................................................. 17 Testes ........................................................................................................................................................... 17 Documentação ............................................................................................................................................. 17
2.5 Metodologias de Desenvolvimento ............................................................................................ 18 Example-Driven Design .............................................................................................................................. 19 Hot Spot Driven Design............................................................................................................................... 19 Metodologia Taligent................................................................................................................................... 20
2.6 Ciclo de vida..................................................................................................................................... 20
3. PADRÕES DE PROJETO ...........................................................................22
4. FRAMEWORK PARA INTERFACE GRÁFICA ...........................................26
4.1 Análise do Domínio............................................................................................................................ 26
4.2 Projeto................................................................................................................................................. 27
4.3 Implementação ................................................................................................................................... 46
7
4.4 Testes................................................................................................................................................... 47
4.5 Documentação .................................................................................................................................... 47
5. FRAMEWORK PARA JOGOS DE CARTAS ..............................................47
5.1 Análise do Domínio............................................................................................................................ 48
5.2 Projeto................................................................................................................................................. 49
5.3 Implementação ................................................................................................................................... 55
5.4 Testes................................................................................................................................................... 55
5.5 Documentação .................................................................................................................................... 58
6. CONCLUSÕES............................................................................................59
7. SUGESTÕES DE TRABALHOS FUTUROS ...............................................60
8. REFERÊNCIAS BIBLIOGRÁFICAS............................................................62
9. APÊNDICES ................................................................................................63
9.1 Guia para a criação de aplicações .................................................................................................... 63
10. ANEXOS......................................................................................................67
10.1 Código Fonte ...................................................................................................................................... 67
ARTIGO ................................................................................................................76
8
Lista de Figuras
FIGURA 3. DIAGRAMA DE CLASSES DO PADRÃO DE PROJETO OBSERVER............................... 24 FIGURA 4. DIAGRAMA DE CLASSES DO PADRÃO DE PROJETO SECOND-GUESS. .................... 25 FIGURA 5. CASOS DE USO PARA O FRAMEWORK DE INTERFACE GRÁFICA............................ 27 FIGURA 6. RELACIONAMENTO ENTRE O MODELO E SUA VISÃO. .............................................. 28 FIGURA 7. INTERAÇÃO DINÂMICA ENTRE UM MODELO E A SUA VISÃO................................. 29 FIGURA 8. HIERARQUIA DE MODELOS PRESENTE NO FRAMEWORK DE INTERFACE
GRÁFICA................................................................................................................................................ 30 FIGURA 9. HIERARQUIA DE VISÕES PRESENTE NO FRAMEWORK DE INTERFACE GRÁFICA.
31 FIGURA 10. EXEMPLO DE RELACIONAMENTOS NA CRIAÇÃO DE VISÕES. ............................ 32 FIGURA 11. EXEMPLO DE INTERAÇÃO NA CRIAÇÃO DE VISÕES.............................................. 32 FIGURA 12. HIERARQUIA DE GERENTES DE DISPOSIÇÃO DE VISÕES. .................................... 33 FIGURA 13. SEQÜÊNCIA DE CHAMADAS PARA A GERÊNCIA DE DISPOSIÇÃO...................... 34 FIGURA 14. CLASSES QUE PARTICIPAM DO MECANISMO DE DESENHO. ............................... 35 FIGURA 15. SEQÜÊNCIA DE CHAMADAS PARA O MECANISMO DE DESENHO ...................... 37 FIGURA 16. HIERARQUIA DE EVENTOS ........................................................................................... 38 FIGURA 17. CLASSES QUE PARTICIPAM DO DISPARO DE EVENTOS. ....................................... 39 FIGURA 18. VISÃO DINÂMICA DO DISPARO DE UM EVENTO..................................................... 39 FIGURA 19. SUPORTE A VISUALIZAÇÃO DO OBJETO ARRASTADO.......................................... 41 FIGURA 20. CLASSES PARTICIPANTES DE UM DRAG AND DROP.............................................. 43 FIGURA 21. SEQÜÊNCIA DE CHAMADAS PARA UMA AÇÃO DE DRAG AND DROP. .............. 44 FIGURA 22. HIERARQUIA DE IMPLEMENTAÇÕES DE FRAMEIMPL............................................ 46 FIGURA 23. HIERARQUIA DE IMPLEMENTAÇÕES DE DRAWMANAGER. ................................... 46 FIGURA 24. APLICAÇÃO DESENVOLVIDA SOB O FRAMEWORK DE JOGOS DE CARTAS..... 48 FIGURA 25. CASOS DE USO PARA O FRAMEWORK DE JOGOS DE CARTAS............................. 49 FIGURA 26. HIERARQUIA DE MODELOS PRESENTE NO FRAMEWORK DE JOGOS DE
CARTAS. 50 FIGURA 27. HIERARQUIA DE CONDIÇÕES PARA ADIÇÃO DE CARTAS EM UM
PILEOFCARDS....................................................................................................................................... 51 FIGURA 28. HIERARQUIA DE CONDIÇÕES PARA REMOÇÃO DE CARTAS EM UM
PILEOFCARDS....................................................................................................................................... 52 FIGURA 29. ESTRUTURA DE CLASSES UTILIZADA PARA A DISTRIBUIÇÃO DE CARTAS. ... 53 FIGURA 30. MODELAGEM DINÂMICA DA DISTRIBUIÇÃO DE CARTAS.................................... 54 FIGURA 31. CLASSE BÁSICA DE UM JOGO DE CARTAS. .............................................................. 55 FIGURA 32. SCREENSHOT DO JOGO FREECELL. ............................................................................ 56 FIGURA 33. SCREENSHOT DO JOGO PACIÊNCIA............................................................................ 58
9
Acrônimos e Abreviaturas
• API - Application Programming Interface (Interface para Programação de
Aplicativos)
• GUI - Graphical User Interface (Interface Gráfica com o Usuário)
• MVC - Model-View-Controller (paradigma para o desenvolvimento de interfaces
gráficas)
• OO - Orientação a Objeto
• OOAD - Object-Oriented Analisys and Design (Análise e Projeto Orientados a
Objetos)
• SDK - Software Development Kit (Kit de Desenvolvimento de Software)
• UFSC - Universidade Federal de Santa Catarina
10
1. Introdução
Há décadas, a reusabilidade vem sendo uma das principais metas dos engenheiros de
software. Entretanto, reusar software não é nada simples e a maior parte dos esforços nesse
sentido acabou gerando apenas pequenos componentes caixa-preta. Com a popularização
do paradigma OO, tornou-se possível desenvolver componentes reusáveis de maior
granularidade, entre eles os frameworks OO. Atualmente existem frameworks para uma
grande variedade de domínios, entre eles podemos citar o MVC do Smalltalk e o AWT do
Java.
1.1 Tema
Desenvolvimento de frameworks com o uso de padrões de projeto.
1.2 Delimitação do Tema
O tema será trabalhado através do desenvolvimento de um framework específico
para jogos de carta.
1.3 Objetivo Geral
Explorar conceitos e técnicas de desenvolvimento de frameworks.
1.4 Objetivos Específicos
• Explorar as vantagens do uso de padrões de projeto no desenvolvimento de
aplicações.
• Criação de uma estrutura que facilite o desenvolvimento de aplicações no
domínio de jogos de cartas.
11
• Criação de aplicações sob o framework desenvolvido.
1.5 Motivação
Vivemos hoje o paradoxo da indústria de software, isto é, a indústria de software
sente a necessidade de produzir programas altamente complexos no menor tempo e com o
menor custo possível. Um dos caminhos para a solução desse problema é o reuso: artefatos
de software reusáveis são produzidos e disponibilizados.
Frameworks são artefatos reusáveis de alta granularidade. A utilização de um
framework tem como objetivo a redução no tempo e no custo de criação e manutenção de
novas aplicações que pertençam ao domínio tratado por esse framework. Além disso, a
utilização desses artefatos de software resulta em aplicações mais confiáveis, pois, estando
o framework depurado, o desenvolvimento deixa pouca margem à inserção de erros.
12
2. Frameworks Orientados a Objetos
"A framework is a set of classes that embodies an abstract design for solutions to a
family of related problems.1"
- Johnson-Foote 1988
Apesar de todos os avanços tecnológicos das últimas décadas, o projeto e a
implementação de aplicações complexas continuam caros e suscetíveis a erros. Grande
parte do custo e esforço de desenvolvimento dessas aplicações provém do constante
redescobrimento e reinvenção de conceitos e artefatos de software.
O framework OO é uma técnica de reuso de projeto e implementação com o objetivo
de reduzir o custo e melhorar a qualidade do software produzido.
2.1 Conceitos Básicos
Um framework é um conjunto de classes integradas que define uma estrutura
reusável para um domínio específico de aplicações. O framework provê uma arquitetura
particionada em classes abstratas e define as responsabilidades e um modelo de
colaboração para essas classes. O desenvolvedor adapta o framework para uma aplicação
particular especializando e agregando instâncias de suas classes. A figura abaixo ilustra
uma aplicação desenvolvida a partir de um framework. A estrutura em azul corresponde ao
framework e o restante é o que foi produzido pelo usuário do framework no
desenvolvimento dessa aplicação específica.
Figura 1. Uma aplicação desenvolvida sob um framework OO.
1 Um framework é um conjunto de classes que encorporam um projeto abstrato para a solução de uma família de problemas correlacionados.
13
Frameworks portam a infra-estrutura de projeto, característica que reduz a
quantidade de código a ser desenvolvida na criação de aplicações. O modelo de
colaboração entre as classes de um framework define a arquitetura da aplicação livrando o
desenvolvedor dessa responsabilidade.
A arquitetura dinâmica de um framework é caracterizada por uma inversão de
controle. Essa arquitetura permite que o comportamento das aplicações seja alterado por
handlers2 invocados pelo framework. Quando determinado evento ocorre, o framework
reage invocando métodos em handlers pré-determinados, o que permite um processamento
de eventos específico para cada aplicação. A inversão de controle permite que o framework
(e não a aplicação) determine que métodos invocar em resposta a eventos externos
(princípio de Hollywood: "Don't call us, we'll call you3").
2.2 Vantagens
O uso de frameworks traz diversas vantagens para o desenvolvimento de aplicações:
Modularidade
Frameworks provêm modularidade, encapsulando implementações voláteis em
interfaces estáveis. A modularidade do framework ajuda a melhorar a qualidade do
software produzido, diminuindo o impacto de mudanças no projeto ou na implementação.
Além disso, com a modularidade reduz-se o esforço para entender e modificar aplicações
existentes.
Reusabilidade
As interfaces estáveis providas por um framework aumentam a reusabilidade
definindo estruturas genéricas que podem ser reutilizadas em diversas aplicações. O reuso
da estrutura de um framework gera grandes melhoras na produtividade e aumenta a
2 Manipuladores 3 “Não ligue para nós, nós ligaremos para você.”
14
qualidade, performance, robustez e interoperabilidade das aplicações geradas.
Extensibilidade
Um framework melhora a extensibilidade através dos métodos hook4 e dos hot spots5
que permitem que uma aplicação aumente suas interfaces estáveis. A extensibilidade é
essencial para garantir a adaptação de características e comportamentos genéricos de um
domínio de aplicações a uma aplicação específica.
2.3 Desafios
Quando usado em conjunto com padrões, bibliotecas de classes e componentes, os
frameworks podem aumentar significativamente a qualidade da aplicação produzida e
reduzir o esforço de desenvolvimento. Entretanto, vários desafios devem ser vencidos
durante o desenvolvimento de um framework:
Esforço de desenvolvimento
O desenvolvimento de um framework genérico, extensível, reusável e de qualidade
para um domínio qualquer é um grande desafio e exige uma quantidade considerável de
esforço e tempo.
Desenvolver um framework para generalizar um domínio de aplicações muito
simples pode não compensar. Deve-se fazer uma análise detalhada da relação custo
benefício da criação de um framework antes de começar seu desenvolvimento.
O grande esforço desprendido no desenvolvimento de um framework é amenizado
pela rapidez com que aplicações de qualidade são desenvolvidas sob este framework.
4 Métodos hook formam, em conjunto com métodos template, um ponto de flexibilidade. Métodos hook podem ser redefinidos pela aplicação e são utilizados pelos métodos templates que implementam de forma genérica um algoritmo. 5 “Hot spots são as partes da estrutura de classes de um framework que devem ser mantidas flexíveis, para possibilitar sua adaptação a diferentes aplicações do domínio.” [SIL 98]
15
Curva de Aprendizado
Assim como o esforço de desenvolvimento, o esforço necessário para aprender a usar
um framework OO é considerável. Entretanto, depois da fase de aprendizado é possível
começar o desenvolvimento de aplicações imediatamente.
Integração
O desenvolvimento de aplicações de grande porte pode ser baseado na integração de
diversos frameworks (um framework para a GUI e outro para a gerência de dados, por
exemplo). Essa integração nem sempre é fácil, pois a maioria dos frameworks são
desenvolvidos visando a extensibilidade interna e não a integração com outros frameworks.
Manutenção
A manutenção de frameworks é trabalhosa, pois é necessário um conhecimento
profundo da estrutura do framework e das relações entre suas classes.
Validação
Validar aplicações construídas a partir de frameworks é muito difícil. É praticamente
impossível distinguir os bugs do framework dos bugs da aplicação sendo desenvolvida.
Além disso, a inversão de controle do framework dificulta a depuração.
Eficiência
Frameworks aumentam a extensibilidade através de níveis adicionais de abstração.
Essa característica diminui ligeiramente a eficiência das aplicações desenvolvidas sob o
framework.
Frameworks não devem ser usados para a generalização de domínios onde o
principal requisito é a eficiência, como por exemplo, aplicações em tempo real.
Falta de Padrões
16
Atualmente, não existe um padrão para o projeto, desenvolvimento, documentação e
uso de frameworks. Dentre as metodologias de desenvolvimento existentes, nenhuma pode
ser considerada um padrão. Essa característica prejudica o desenvolvimento, manutenção e
uso de frameworks.
2.4 Desenvolvimento de Frameworks
O desenvolvimento de um framework é ligeiramente diferente do desenvolvimento
de uma aplicação comum. A grande diferença é que um framework tem que cobrir os
conceitos relevantes a um domínio enquanto uma aplicação cobre apenas os conceitos
mencionados nos seus requisitos. O desenvolvimento de um framework pode ser dividido
da seguinte maneira:
Análise do Domínio
A análise de domínio é o processo de identificação e organização de conhecimentos a
respeito de uma classe de problemas – um domínio de aplicações – para suportar a
descrição e solução desses problemas. É um passo fundamental na criação de artefatos de
software reusáveis, pois elementos gerados através de uma análise de domínio capturam a
funcionalidade essencial requerida por um domínio.
Uma análise de domínio é um passo importante no desenvolvimento de frameworks,
pois faz com que o desenvolvedor do framework obtenha uma maior compreensão dos
conceitos do domínio, funcionalidades e hot spots que um framework deve possuir.
Modelagem
A modelagem de um framework consiste na especificação da estrutura de classes
desse framework. Essa estrutura de classes deve possuir algumas características
importantes:
• Generalidade – reflete a capacidade do framework de alterar suas
funcionalidades, tendo em vista as necessidades de uma aplicação específica.
17
• Extensibilidade – refere-se à manutenibilidade do framework. O
desenvolvimento de frameworks é um processo iterativo, isto é, à medida que
o framework é utilizado novos recursos podem ser agregados a sua estrutura.
Assim, durante o projeto de um framework, deve-se tentar prever futuras
utilizações para este framework e futuras extensões no domínio tratado.
Implementação
A implementação de frameworks segue as linhas gerais da implementação de uma
aplicação comum. Todas as técnicas e padrões para o desenvolvimento de código podem
ser usados na implementação de um framework.
É importante ressaltar que o sucesso da implementação de um framework depende da
qualidade do projeto desse framework. Todas as características definidas durante a fase de
projeto devem ser mantidas na fase de implementação para que o framework não perca sua
capacidade de generalizar um domínio de aplicações.
Testes
Um framework é validado através da criação de aplicações teste, usadas para
determinar se o framework provê as funcionalidades desejadas e para avaliar sua
usabilidade.
Se, no processo de criação de aplicações, forem encontradas características do
domínio tratado que não estão presentes no framework deve-se reavaliar o projeto e
atualizar a implementação do framework para que essas características sejam adicionadas.
Essa retro-alimentação faz parte do ciclo de vida dos frameworks.
Documentação
O desenvolvimento e a utilização de frameworks são tarefas complexas e, por isso,
uma boa documentação é essencial. A documentação deve prover informações sobre o
domínio tratado pelo framework, sua estrutura e funcionamento.
18
Frameworks podem ser descritos a partir de notações de metodologia OOAD como,
por exemplo, a UML. Essa descrição pode ser uma ótima fonte de informação sobre o
framework e, portanto, uma documentação valiosa.
Uma outra forma de documentação é a que ensina a usar o framework para gerar
aplicações. Esse tipo de documentação dá pouca ênfase a aspectos de projeto
concentrando-se na descrição do processo de criação de aplicações sob o framework. Um
exemplo desse tipo de documentação é o cookbook.
Cookbooks são conjuntos de receitas textuais para a utilização de um framework.
Sua principal vantagem é a capacidade de responder a questões chave minimizando o
tempo gasto para produzir aplicações. O principal problema dessa abordagem é que ela
cobre uma faixa limitada de tipos de aplicação. O auxilio proveniente do uso de um
cookbook pode ser ínfimo no caso das necessidades do usuário não se ajustarem ao
conjunto de problemas tratados pelo cookbook. [SIL 00]
Outro modo de documentar um framework, talvez o mais elementar, é a
disponibilização de código fonte aos usuários. O código fonte de um framework ou de
aplicações desenvolvidas sob esse framework é uma rica fonte de documentação.
Entretanto, é muito difícil entender o funcionamento de um framework apenas pelo seu
código fonte e, por isso, é recomendável que o código não seja a única fonte de referência
disponibilizada pelo autor do framework.
2.5 Metodologias de Desenvolvimento
Uma metodologia de desenvolvimento de frameworks consiste em procedimentos
que abrangem a captura das características de um domínio e a construção e teste da
estrutura de um framework. Essas metodologias se caracterizam por estabelecer o processo
de desenvolvimento de frameworks em linhas gerais, sem se ater à definição de técnicas de
modelagem ou detalhar o processo de desenvolvimento.
Existem poucas metodologias para o desenvolvimento de frameworks, dentre as
19
quais podemos identificar três principais: example-driven design, hot spot driven design e a
metodologia Taligent.
Example-Driven Design
A metodologia “projeto dirigido por exemplos” (example-driven design) é
caracterizada pela análise de aplicações já desenvolvidas no domínio tratado, com o
objetivo de descobrir as características desse domínio. A abstração do domínio é obtida a
partir da generalização.
O processo de generalização ocorre a partir da busca de elementos semelhantes nas
diferentes aplicações de um domínio. Recorre-se a parametrização para eliminar as
diferenças. Elementos semelhantes de diferentes aplicações podem dar origem a classes
abstratas que agrupam as semelhanças.
Idealmente, no mínimo quatro aplicações existentes em um domínio deveriam ser
analisadas para que as semelhanças entre elas sejam identificadas. Entretanto, devido à
dificuldade de encontrar tantas aplicações já desenvolvidas num domínio especifico, pode-
se recorrer a um outro procedimento um pouco mais simples: o framework e um mínimo
de duas aplicações do domínio são desenvolvidos em paralelo. A troca de informações
durante o desenvolvimento em paralelo auxilia a busca de generalidade no framework.
Hot Spot Driven Design
A metodologia “projeto dirigido por pontos de flexibilização” (hot spot driven
design) enfatiza a busca de hot spots na estrutura de classes de um domínio. Hot spots são
as partes da estrutura de classes de um framework que devem ser mantidas flexíveis para
garantir que o framework cubra as diferentes aplicações do domínio tratado.
O desenvolvimento de frameworks auxiliado pela metodologia Hot Spot Driven
Design segue a seguinte seqüência de procedimentos:
• Identificação de classes – o desenvolvedor de frameworks, a partir das
informações obtidas na analise do domínio, define uma estrutura de classes
20
inicial para o framework.
• Identificação de hot spots – é preciso identificar que aspectos diferem de
aplicação para aplicação e definir o grau de flexibilidade que deve ser
mantido em cada caso.
• Projeto do framework – modificação da estrutura de classes definida no inicio
do processo, de modo a comportar a flexibilidade requerida.
• Refinamento da estrutura do framework – refinamento da estrutura de classes
do framework a partir das informações obtida na analise do domínio. Nessa
etapa, se o framework não for flexível o suficiente para generalizar as
aplicações do domínio, deve-se voltar a etapa de definição dos hot spots.
Metodologia Taligent
A metodologia Taligent, proposta pela empresa de mesmo nome, difere das
anteriores pelo conjunto de princípios que norteiam o desenvolvimento de frameworks.
Primeiramente, a visão de desenvolver um framework que cubra as características e
necessidades de um domínio é substituída pela visão de produzir um conjunto de
frameworks estruturalmente menores e mais simples, que usados em conjunto, darão
origem às aplicações. Pequenos frameworks são mais flexíveis e podem ser reutilizados
mais freqüentemente. Assim, a ênfase passa a ser o desenvolvimento de frameworks
pequenos e direcionados a aspectos específicos do domínio.
Um outro aspecto é que uma das linhas mestras do desenvolvimento é tornar o uso
do framework o mais simples possível, através da minimização da quantidade de código
que o usuário deve produzir, através da disponibilidade de classes concretas que o usuário
pode usar diretamente, minimização do número de classes que devem ser criadas e
minimização do número de métodos que devem ser sobrepostos. [SIL 00]
2.6 Ciclo de vida
O ciclo de vida do desenvolvimento dos frameworks é diferente dos ciclos de vida
tradicionais para o desenvolvimento de aplicações comuns. Um framework não pode ser
considerado acabado. O desenvolvimento de aplicações sob frameworks invariavelmente
21
vai revelar características do domínio que não foram tratadas. Cabe ao desenvolvedor
refinar o framework para que esse possa ser o mais abrangente possível.
Figura 2. O ciclo de vida do desenvolvimento de frameworks.
Análise do Domínio
Modelagem do Framework
Implementação do Framework
Refinamento do Framework
Aplicações sob o Framework
22
3. Padrões de Projeto
"Each pattern describes a problem which occurs over and over again in our
environment, and then describes the core of the solution to that problem, in such a way that
you can use this solution a million times over, without ever doing it the same way twice.6”
- Christopher Alexander
Apesar do texto acima se referir a padrões usados por arquitetos para a descrição de
prédios e cidades, a mesma definição se aplica em sistemas orientados a objetos. Um
padrão de projeto nomeia e explica sistematicamente uma solução geral para um problema
recorrente em sistemas OO.
O padrão de projeto descreve o problema, a solução, quando aplicar a solução e as
conseqüências de sua aplicação. A solução é uma estrutura de classes e objetos que resolve
o problema. Uma definição mais específica para o âmbito de software OO seria:
“descriptions of communicating objects and classes that are customized to solve a general
design problem in a particular context7” [GAM 94].
Padrões de projeto capturam a essência de uma idéia que projetistas experientes
usaram diversas vezes para resolver um problema comum, abstraindo-se da situação
específica. Por isso, os padrões de projeto são independentes da aplicação e tem que ser
mapeados para uma situação específica antes de serem implementados.
De forma geral, um padrão de projeto possui os seguintes elementos [GAM 94]:
Nome do padrão
Encontrar um nome adequado é um dos passos mais importantes durante a descrição
de um novo padrão encontrado. O nome é utilizado como referência a um problema de
projeto, a sua solução e as suas conseqüências. Com ele é possível simplificar a
documentação, facilitar a comunicação entre membros de uma equipe de trabalho e 6 "Cada padrão descreve um problema que ocorre repetidamente em nosso meio, depois descreve o núcleo da solução para o problema de forma que se possa usar esta solução milhões de vezes, porém nunca da mesma forma."
23
projetar num nível de abstração maior.
Descrição do Problema
Identifica qual o problema que o padrão visa solucionar e em que contexto ele pode
ser aplicado.
Solução
Descreve o padrão, isto é, os elementos que o constituem, seus relacionamentos e
responsabilidades. Como um padrão deve poder ser aplicado nas mais diferentes situações,
a descrição não pode ser baseada num exemplo concreto. Ao contrário, deve de forma
abstrata mostrar a organização geral da estrutura de classes e objetos que resolvem o
problema. Porém, pode ser incluído na descrição um exemplo de uma aplicação do padrão
a titulo de exemplo.
Conseqüências do uso
Indica as conseqüências da aplicação de um determinado padrão de projeto. Pode
incluir o impacto que o padrão traz em termos de flexibilidade, extensibilidade e
portabilidade, informações sobre trade-off de espaço e tempo e questões sobre
implementação em alguma linguagem de programação específica. Explicitar essas
considerações é importante, pois certas características de um padrão podem torná-lo
inapropriado a um contexto específico, como por exemplo, uma aplicação que demande
um tempo de resposta muito baixo. Além disso, se mais de uma solução estiver disponível,
essas informações podem ser usadas para a seleção da opção mais apropriada.
Existem padrões de projeto que são genéricos o suficiente para permitir a sua
aplicação nos mais diferentes domínios. No livro [GAM 94] foram catalogados vários
padrões genéricos. Porém cada domínio possui a sua própria coleção de padrões, que
podem ser também catalogados e descritos. Dois padrões de projeto estão expostos a seguir
exemplificando essas duas possibilidades. O primeiro padrão apresentado, de uso genérico, 7 "descrições de objetos e classes que se comunicam e que podem ser adaptados para resolver um problema
24
é conhecido como Observer e foi retirado do livro [GAM 94]; o segundo, utilizado em
aplicativos de tempo real com controle contra falha, conhecido como Second-Guess foi
retirado de [DOU 99]:
Padrão Observer
Sub ject
attach(Observer)detach(Observer)notify()
Observer
update()
**
Concrete Observer
Concrete Subject
s tate
modifyState()getState()
+observers
+subject
Figura 3. Diagrama de classes do padrão de projeto Observer.
O padrão Observer permite remover a acoplamento entre um objeto que possui um
estado qualquer e outros objetos que dependam desse estado e que, portanto precisam ser
notificados quando ele mudar.
Esse padrão poderia ser aplicado, por exemplo, em um aplicativo que possui um
conjunto de dados qualquer e permite a visualização desses dados de mais de uma forma,
possivelmente simultâneas (tabela e gráfico de barras, por exemplo). Sempre que os dados
mudarem as suas representações devem ser avisadas e atualizadas.
geral de projeto num contexto específico"
25
Padrão Second-Guess
Concrete Algorithm 1
Concrete Algorithm 2
Context
doSomething()
Algorithm
calculate()
#primaryCalculation
#secundaryCalculation
doSomething(){ prim aryCalculation.calculate() secundaryCalculation.calculate() if (results not s im ilar) take corrective action}
Figura 4. Diagrama de classes do padrão de projeto Second-Guess.
Esse padrão permite a utilização de duas implementações diferentes de um mesmo
algoritmo como forma de aumentar a confiabilidade do sistema. Ambos os algoritmos são
acionados e, caso haja uma diferença muito grande entre os resultados, uma condição de
erro foi detectada e alguma ação de correção pode ser executada.
Um exemplo de uso seria um sistema de controle de tráfego aéreo, onde o cálculo da
distância entre duas aeronaves é implementado de forma diferente por dois algoritmos.
Quando essa informação é requisitada, ambos são invocados. Caso algum algoritmo
apresente um valor errado (devido a um acúmulo de erros de arredondamento, por
exemplo), essa condição é detectada e um alarme pode ser acionado.
26
4. Framework para Interface Gráfica
Durante o desenvolvimento do framework para jogos de carta, sentiu-se a
necessidade de criar uma estrutura de manipulação gráfica que desse suporte as seguintes
características:
• Independência de plataforma e linguagem
• Facilidade na implementação de estruturas gráficas estáticas
• Suporte a drag-and-drop para objetos gráficos complexos
Essa estrutura foi projetada visando extensibilidade, generalidade e simplicidade na
implementação dos aplicativos.
4.1 Análise do Domínio
Todos os sistemas operacionais que possuem interface gráfica disponibilizam uma
API para a criação de aplicativos gráficos. Geralmente, essa API consiste em um conjunto
de funções básicas para a construção de janelas, manipulação e controle de eventos, etc.
Normalmente, essa API é difícil de ser usada e, por isso, muitas vezes encontramos
uma camada intermediária embutida nas linguagens de programação que torna o
desenvolvimento mais simples e rápido. O mais famoso exemplo desse tipo de
intermediação é o VisualBasic da Microsoft.
Para obter uma melhor compreensão do domínio, estudou-se o suporte
disponibilizado por diversas linguagens. Por ser um sistema novo, orientado a objeto e com
código disponível, a principal fonte de referência foram os pacotes AWT/SWING do
Java® 1.4.
O diagrama abaixo lista os casos de uso com a visão de um usuário utilizando uma
aplicação desenvolvida sob o framework.
27
Executar operações com o teclado
Arrastar componentes (drag)
Executar operações com o mouse
<<uses>>
Usuário da aplicação
Receber visualização gráfica
Figura 5. Casos de uso para o framework de interface gráfica.
O projeto desse framework, portanto, deve prover recursos que permitam ao
desenvolvedor da aplicação definir o comportamento de cada caso de uso acima descrito
de forma a implementar o seu aplicativo.
4.2 Projeto
Model-View-Controller
O Model corresponde a uma entidade ou abstração que vem diretamente do domínio
da aplicação ou da sua implementação. Este modelo não deve possuir informações externas
a ele, como, por exemplo, uma representação específica que será mostrada ao usuário.
Informações sobre o modo de exibição ficam numa classe separada (View). Mais de um
tipo de visualização é possível para cada modelo. Essa separação aumenta a reusabilidade
do modelo e permite a sua utilização nos mais diferentes contextos. Essa estrutura segue o
padrão de projeto Observer.
28
Todas as entidades que podem ser manipuladas diretamente pelo usuário da
aplicação devem ser subclasses de Model. Para cada modelo criado onde se deseja uma
apresentação gráfica deverá ser criado um View específico.
A classe View representa um elemento qualquer que possua uma representação
gráfica e que possa receber estímulos do usuário. Sempre que o modelo mudar, sua
visualização deve ser também alterada, refletindo a mudança.
A cada View podem estar associados vários EventHandlers, responsáveis pelo
tratamento de eventos. A associação existente entre View e EventHandlers implementa o
padrão Strategy, permitindo a utilização de vários algoritmos de controle sem criar uma
associação forte entre a visualização e o controlador.
Para o usuário da aplicação desenvolvida, só a View importa, pois é por onde ele
receberá as informações e por onde ele interagirá com elas (através de dispositivos de
entrada como Mouse e teclado). Para o desenvolvedor da aplicação (usuário do
framework), apenas os Model importam, pois, conforme será explicado adiante, as Views
são automaticamente criadas pelo sistema.
Model
Model()addView(view : View) : voidnotifyViewers(changeInfo : Object) : voidremoveView(view : View) : void
EventHandler
handleCommonMouseEvent()handleDragEvent()handleKeyboardEvent()
View
View(model : Model)getModel() : Modelupdate(changeInfo : Object) : void
**#model
**
Figura 6. Relacionamento entre o modelo e sua visão.
Abaixo está descrito a dinâmica do processo de notificação entre Model e View
através de um exemplo real retirado do framework para jogos de cartas.
29
Este exemplo parte da existência de uma carta (que é subclasse de Model) qualquer
no sistema. Para ela são criadas duas Views quaisquer8. Quando a carta é modificada, esta
avisa cada visão associada a ela, de forma que estas possam se atualizar.
Para melhorar a performance da atualização, junto com a notificação de que o Model
mudou também pode ser enviado um objeto que contém informações completas de qual
mudança ocorreu.
client aModel : FourSuitCard
view1 : FourSuitCardView
view2 : FourSuitCardView
setAttribute("Suit", FourSuitDeck.SPADES)notifyViewers(SUIT_CHANGED)
FourSuitCardView(aModel)
addView(view1)
FourSuitCardView(aModel)
addView(view2)
update(SUIT_CHANGED)
getAttribute("Suit")
update(SUIT_CHANGED)
getAttribute("Suit")
Figura 7. Interação dinâmica entre um modelo e a sua visão.
Hierarquia de Classes – Model
Os modelos dividem-se em AttributedModel e Attribute. Os Attributes são modelos
simples que representam atributos dos modelos mais complexos. O uso de um modelo
especial para representar atributos facilita a manipulação dos mesmos. 8 A criação de Views nesse exemplo é feita de forma manual, por simplicidade. Normalmente esse passo é feito automaticamente pelo framework.
30
AttributedModels são os modelos complexos. Cada uma de suas características está
representada como um atributo. O usuário do framework pode estender essa classe para a
criação de modelos específicos, caso necessário. É sugerido que, quando o usuário for criar
um novo modelo9, essa classe sirva como base. Essa abordagem, porém, não é obrigatória.
O usuário pode criar novos modelos como subclasses de Model diretamente, caso desejado.
As classes PanelModel e ViewConfigurator possuem um papel fixo no framework e
serão explicadas a seguir.
AttributedModel
addAttribute()getAttribute()setAttribute()
IntegerAttrvalue : int = 0
PictureAttrfileName : String
StringAttrstring : String
Attribute
getType()change()isCompatible()
PanelModel
addModel()changeHandler()changeModel()getNumberOfModels()removeModel()
HOT SPOT
HOT SPOT
ViewConfiguration
CompositeModel
setViewFactory()addModel()removeModel()getNumberOfModels()getModel()
Model
Figura 8. Hierarquia de modelos presente no framework de interface gráfica.
Hierarquia de Classes – View
Cada Model que é mostrado ao usuário da aplicação deve possuir uma View
correspondente. Sempre que um usuário criar um novo Model ele deve também criar uma
View.
O diagrama abaixo mostra algumas visões disponíveis no framework.
9 O apêndice descreve como os hot spots definidos devem ser implementados.
31
PanelView
PictureView HOT SPOT
ProxyView
Viewview
CompositeView
**
Figura 9. Hierarquia de visões presente no framework de interface gráfica.
Criação de visões
As visões são automaticamente criadas a partir de uma ViewFactory, que possui um
mapa dizendo qual View deve ser usada para cada Model.
Cada visão possui um objeto da classe ViewConfiguration que pode ser usado para a
sua configuração. Uma ViewFactory pode então armazenar a configuração que deve ser
usada quando for criar uma visão para um modelo qualquer. ViewFactory é um HotSpot
pois o usuário pode criar novas estratégias para a criação de Views. Isso porém demanda
um conhecimento muito maior a respeito do funcionamento interno do framework do que o
necessário para a criação de aplicações.
O ViewConfiguration representa os atributos da visão que podem ser configurados,
como por exemplo cor, transparência, etc. Cada View pode possuir os seus próprios
parâmetros de configuração. Uma visão para um bloco de texto pode ter a opção de definir
se o texto deve rolar automaticamente quando ele for maior do que o espaço disponível na
tela, por exemplo. A classe ViewConfigurator é subclasse de AttributedModel e pode
portanto possuir uma visão específica e um método uniforme de acesso e definição dos
seus atributos. Isso permite a criação de ferramentas gráficas que podem facilitar a
configuração de visões.
32
DefaultViewFactory
DefaultViewFactory()createViewFor()
CompositeView
addView()ViewFactory
createViewFor()
#viewFactory
PanelView
modelChanged()
PanelModel
addModel()
#model
HOT SPOT
View
getViewConfiguration()
Figura 10. Exemplo de relacionamentos na criação de visões.
Abaixo a visão dinâmica da criação de uma visão para um Model qualquer.
client aPanel : PanelModel
viewForAPanel : PanelView
: ViewFactory
addModel(model)update()
getModel()
createViewFor(model)
addView(view)
Figura 11. Exemplo de interação na criação de visões.
33
Gerentes de disposição
A classe PanelView, subclasse de CompositeView, usa um LayoutManager para
configurar o modo como suas visões estão dispostas na tela. Por exemplo, um PanelView
usando um PileLayout vai empilhar todas as suas visões. LayoutManager é um hot spot
pois é possível criar diferentes tipos de gerentes de disposição.
PanelViewLayoutManager
doLayout()
#panelView #layoutManager
PileLayout SpacedLayoutXYLayout HOT SPOT
Figura 12. Hierarquia de gerentes de disposição de visões.
A seguir o funcionamento básico de uma operação de gerenciamento de disposição.
Quando algum atributo do PanelView é alterado (um novo elemento foi adicionado, por
exemplo), este chama fixLayout(), que reorganiza as visões dentro do painel.
34
: PanelView : LayoutManager
: View
fixLayout( )doLayout( )
getNumberOfViews( )
getView()
setBounds()
getBounds( )
Para cada View, calcule posição e defina.
Figura 13. Seqüência de chamadas para a gerência de disposição
Mecanismo de desenho
O diagrama de classes abaixo ilustra o relacionamento entre as classes envolvidas no
desenho da interface gráfica.
• Surface é uma superclasse abstrata que pode ser passada ao DrawScheduler,
que chama um método quando esta superfície precisar ser desenhada.
• DrawScheduler é responsável por diminuir a freqüência com que uma
superfície sofre uma operação de desenho. Mudanças seqüenciais num curto
espaço de tempo são agregadas em uma única operação de desenho,
melhorando a performance do sistema.
• Um Frame é uma janela gráfica que possui a superfície de desenho que será
mostrada ao usuário. Como a implementação dessa classe é dependente de
plataforma, a classe FrameImpl encapsula a parte específica.
• O Frame possui um ImageBuffer, que é uma área de memória onde uma
figura pode ser armazenada. Essa figura é usada para armazenar a imagem
gerada numa operação de desenho. Quando alguma View muda, só a área
afetada pela mudança precisa ser redesenhada.
35
FrameImpl
endScreenUpdate() : voidstartScreenUpdate() : DrawManager
FrameImageBuffer
createDrawManager() : DrawManager
#doubleBuffer
DrawSchedulerSLEEP_AMOUNT : int = 10
schedule()
Surface
drawNow()
**
Figura 14. Classes que participam do mecanismo de desenho.
As funções utilizadas para o desenho de uma visão
ficam agregadas numa classe abstrata chamada
DrawManager. Toda operação de desenho é efetuada
através da chamada das funções definidas por essa classe.
Como cada superfície de desenho pode possuir
métodos distintos para implementar cada operação de
desenho (desenhar em uma área de memória é muito
diferente de desenhar em uma janela de interface), essa
classe possui muitas implementações possíveis. Para cada
superfície de desenho uma implementação específica deve ser gerada. A superfície de
desenho deve então prover um método (padrão de projeto Factory Method) para a criação
de um objeto DrawManager compatível.
A view mantém uma lista de áreas danificadas, isto é, áreas
onde alguma mudança ocorreu e que portanto deve ser
redesenhada. Essas áreas são redesenhadas quando o método
drawDamagedAreas() é invocado. O framework é responsável
por invocar esse método, de forma a seguir a hierarquia de visões
dentro de uma janela. Dessa forma uma View que está sobre outra
só será desenhada após essa última.
A função drawDamagedAreas() é redefinida no CompositeView de forma a repassar
o comando de desenho a todas as views que se encontram sobre a área afetada.
A seguir um exemplo da interação entre objetos num evento de atualização da
DrawManager
clearArea()drawImage()drawString()setColor()
JavaDrawManager
JavaOffscreenDrawManager
CompositeView
View
addDamagedArea()drawDamagedAreas()isDamaged()
36
interface. O exemplo supõe que sobre um Frame existe um View que deve ser atualizada
pois o seu Model mudou.
A View afetada então invalida a sua área (marcando como danificada) e chama o
DrawScheduler para agendar uma atualização da superfície onde esta View se encontra.
Quando o DrawScheduler decide que a superfície deve ser atualizada, este chama o
método drawNow() do Frame, que manda a View se redesenhar no seu buffer. Por último o
Frame envia a área redesenhada para o FrameImpl, que mostra o novo estado para o
usuário.
37
: Model : View : DrawScheduler
frame : Frame doubleBuffer : DrawManager
frameImpl : FrameImpl
update()modelChanged()
invalidate( )
addDamagedArea()
schedule(surface)
drawNow( )
Essa chamada ocorre em thread separada, de forma assincrona.
drawDamagedAreas(bufferManager)
drawArea(damagedArea, bufferManager)
createDrawManager(view.bounds)
drawDamagedAreas(clippedDrawManager)
drawArea()
startScreenUpdate( )
endScreenUpdate( )
updateScreen( )
Figura 15. Seqüência de chamadas para o mecanismo de desenho
Controle de Eventos
Para permitir que o usuário interaja com o aplicativo desenvolvido, o desenvolvedor
de aplicações define EventHandlers, que são classes responsáveis por tratar os eventos
feitos pelo usuário.
Quando um usuário clica numa visão qualquer, um evento do mouse é gerado pelo
framework e enviado para o EventHandler associado à visão clicada. Esse handler pode
executar qualquer tipo de ação em resposta ao evento. Por exemplo, se a visão for um
botão, a resposta ao evento pode ser a impressão de um documento.
Vários tipos de eventos são definidos pelo framework, sendo que cada um possui
38
uma implementação específica e um conjunto de atributos associados.
Eventos básicos do mouse (clique, movimentação, etc...) são definidos pela classe
CommonMouseEvent e possui informações como qual botão foi pressionado e qual a
posição em que a ação ocorreu. A posição do evento é sempre relativa à posição superior
esquerda da visão que recebeu o evento. Manter o botão do mouse pressionado em cima de
uma View faz com que a visão sob o mouse passe a ser receptora de todos os eventos
gerados por esse até que o botão seja solto. Essa característica foi observada em todas as
aplicações do domínio estudado e foi portanto replicada.
O Frame possui também a capacidade de identificar o início de uma operação de
drag and drop. O evento DragEvent é enviado quando isso acontece.
Eventos do teclado são enviados através do evento KeyboardEvent e são enviados
para a View que possui o foco no momento do pressionamento da tecla.
DragEventDRAG_ENDEDDRAG_START...
acceptDrop() : booleanwasDragAccepted() : booleanwasDropAccepted() : boolean
MouseEvent
getButton()getX()getY()
CommonMouseEventMOUSE_CLICKMOUSE_MOVE...
getAction()
KeyboardEvent
getButton()getModifiers()
EventHandler
handleCommonMouseEvent()handleDragEvent()handleKeyboardEvent()
Event
callHandler()
Figura 16. Hierarquia de eventos
39
As classes EventHandler e Event se comportam seguindo o padrão de projeto Visitor
de forma a facilitar a chamada do método correto no handler para o evento disparado.
FrameImpl
EventHandler
handleCommonMouseEvent()handleDragEvent()handleKeyboardEvent()
Frame
handleKeyboardEvent()handleMouseEvent()
View
handleKeyboardEvent()handleMouseEvent()
****
Figura 17. Classes que participam do disparo de eventos.
O diagrama de colaboração abaixo mostra a seqüência de ações para o disparo de um
evento do mouse.
: JavaFrameImpl
: Framejava virtual machine
targetContainer : CompositeView
realTarget : View
targetHandler : EventHandler
2: createMouseEvent(javaMouseEvent)4: findTarget(event.coords)
5: convertToChildCoords(event)
7: findTarget()8: convertToChildCoords()
event : CommonMouseEvent
3: handleMouseEvent(event)
6: handleMouseEvent(event)
1: dispatch event(javaMouseEvent)
11: handleCommonMouseEvent()
9: handleMouseEvent(event)
10: callHandler(realTarget, targetHandler)
Figura 18. Visão dinâmica do disparo de um evento.
40
Suporte a Drag and Drop
Criar um suporte genérico a drag and drop foi um dos maiores desafios na construção
do framework para interface gráfica.
Os requisitos definidos foram os seguintes:
• Qualquer Model que possuísse uma visão poderia ser arrastado. Isso exige
um mecanismo eficiente para desenho da visão associada, pois o objeto
arrastado pode ser bastante complexo (como um painel com vários
componentes internos).
• A superfície onde o objeto está sendo arrastado pode mudar, devendo ser
normalmente atualizada.
• Durante o arrasto, se o objeto mudar essa mudança deve ser refletida na
interface. Dessa forma a figura sendo arrastada não é necessariamente
estática.
Eficiência nesse caso é extremamente importante pois durante o drag and drop a
View sendo arrastada deverá ser redesenhada várias vezes por segundo, de forma a garantir
precisão e conforto visual ao usuário da aplicação. De forma a garantir isto, um mecanismo
específico foi adicionado à classe Frame. A visão sendo arrastada é desenhada num buffer
extra e fixo, que é atualizado apenas quando a visão muda. Como o Frame já possui um
buffer com toda a visão da aplicação já desenhada, os seguintes passos são necessários a
cada movimento do mouse. Esses passos são efetuados diretamente na janela do aplicativo
(classe FrameImpl):
1. Cópia do buffer do Frame na área ocupada anteriormente pela View.
2. Cópia do buffer da View na nova área a ser ocupada.
Dessa forma fica garantida a eficiência necessária ao processo.
O diagrama abaixo contém as classes envolvidas no processo de redesenho durante
uma operação de drag and drop.
41
• Frame é responsável por detectar um movimento de drag (mover o mouse
com o botão pressionado) e pela gerência geral da operação, como redesenhar
o objeto quando o mouse se mover e terminar o movimento quando o botão
for liberado.
• DragManager contém informações sobre a operação de drag and drop, tais
como quem foi o iniciador da operação e qual o objeto que está sendo
arrastado.
• DragPanel contém um buffer que armazena a imagem do modelo arrastado e
é responsável por atualizar essa imagem sempre que o modelo mudar. Essa
classe também é responsável por criar uma visão para o modelo.
Surface
drawNow()
Frame
DragManager
endDrag()isDragging()startDrag()
DragPanel
drawDragImage()setDragModel()
View
#viewBeingDragged
Figura 19. Suporte a visualização do objeto arrastado
42
De forma a implementar drag and drop em uma aplicação, o desenvolvedor deve
criar uma ou mais subclasses de DragEventHandler e associá-las a View que irá receber o
início do drag e a todas as Views que podem ser alvo para um drop. Cada ação possível
possui um código específico no DragEvent e deve ser tratado de forma diferente.
Os passos para a definição de um DragEventHandler são os seguintes:
1. Criar uma subclasse de DragEventHandler.
2. Implementar o método dragStart(), que deverá chamar acceptDrag() do
DragEvent() passado por parâmetro. Nessa chamada deve ser definido qual
modelo será usado para a visualização do drag e opcionalmente pode ser
passado um objeto qualquer contendo alguma informação associada ao drag.
Essa informação estará acessível para a View que receber o drop. Deve ser
observado que o protocolo de drag and drop não retira nenhuma View do
sistema, dessa forma, se o objetivo for que a View fonte do drag and drop seja
removida, isso deve ser implementado nesse método.
3. Implementar o método dragDrop(), que deverá chamar acceptdrop() no
evento caso o drop seja aceito. Esse método pode chamar getInfo() para obter
qualquer informação passada durante o drag().
4. Implementar o método dragEnd(). Esse método é chamado na View que
originou o evento, informando se o drop foi aceito ou não. Isso pode ser
usado caso o drag and drop esteja sendo usado para mover um objeto de um
lugar para outro. Se o objeto não foi depositado numa região válida, então ele
deve retornar à posição anterior, por exemplo.
5. Implementar o método dragOver(). Esse passo é opcional e permite, por
exemplo, que algum indicativo (como mudar de cor) de que o drop é aceito
pela View sob o mouse possa ser implementado.
43
Abaixo as classes utilizadas para a implementação de drag and drop.
DragEventHandler
dragDrop()dragEnd()dragOver()dragStart()handleDragEvent()
DragEventDRAG_DROP : int = 3DRAG_END : int = 4DRAG_OVER : int = 2DRAG_START : int = 1
acceptDrag(modelToDrag : Model, info : Object) : voidacceptDrop() : voidgetInfo() : ObjectwasDragAccepted() : booleanwasDropAccepted() : boolean
Figura 20. Classes participantes de um drag and drop.
O diagrama de seqüência abaixo mostra os passos efetuados pelo framework no
decorrer de um evento de drag and drop.
44
Figura 21. Seqüência de chamadas para uma ação de drag and drop.
: Fr
ame
sour
ceV
iew
: V
iew
: D
ragE
vent
Han
dler
drag
Sta
rt :
Dra
gEve
ntdr
agD
rop
: D
ragE
vent
: D
ragM
anag
er :
Dra
gPan
eldr
opV
iew
: V
iew
hand
leM
ouse
Eve
nt(d
ragS
tart)
hand
leD
ragE
vent
()dr
agS
tart(
)
acce
ptD
rag(
mod
elTo
Dra
g)
was
Dra
gAcc
epte
d( )
star
tDra
g(m
odel
ToD
rag)
setD
ragM
odel
()
Um
a V
iew
é
cria
da.
draw
Now
( )ha
ndle
Mou
seE
vent
(mou
seM
ove)
draw
Dra
gIm
age(
)
hand
leM
ouse
Eve
nt(m
ouse
Up)
hand
leM
ouse
Eve
nt(d
ropE
vent
)ha
ndle
Dra
gEve
nt()
drag
Dro
p()
was
Dro
pAcc
epte
d( )
hand
leM
ouse
Eve
nt()
hand
leD
ragE
vent
()dr
agE
nded
()
endD
rag(
)
draw
Dra
gIm
age(
)
45
Suporte a diferentes linguagens e sistemas operacionais
Conforme citado, um dos requisitos propostos foi que o framework desenvolvido
fosse facilmente portado para diferentes plataformas e linguagens. Para garantir isso as
partes que possuem relacionamento direto com o sistema operacional ou com a linguagem
foram isoladas em classes específicas e substituíveis.
Essa classe implementa o padrão de projeto Abstract
Factory. Classes que possuam uma implementação dependente de
plataforma devem incluir um método na ResourceFactory que
será usado para criar um objeto compatível com o sistema utilizado. Dessa forma quando o
framework for portado ou quando uma aplicação estiver executando em um sistema
diferente do original nada precisa ser atualizado.
ImageBuffer representa uma área em
memória que pode ser usada para armazenar
imagens ou gráficos de qualquer tipo. Como cada
sistema possui métodos específicos para a criação
desses objetos, a implementação desse objeto é
específica para a plataforma sendo utilizada.
FrameImpl e DrawManager também
possuem implementações específicas para cada plataforma.
JavaImageBuffer
ImageBuffer
createDrawManager()
WindowsImageBuffer
X11ImageBuffer
ResourceFactory
createFrameImpl()createImageBuffer()
46
Frame
centerInScreen()drawNow()getScreenSize()
FrameImpl
endScreenUpdate()getScreenSize()startScreenUpdate()
WindowsSurfaceImplJavaFrameImpl
Figura 22. Hierarquia de implementações de FrameImpl.
DrawManager
drawImage()drawString()
JavaDrawManager
JavaOffscreenDrawManager
X11DrawManager
Figura 23. Hierarquia de implementações de DrawManager.
4.3 Implementação
Durante a implementação usou-se o ambiente de desenvolvimento Jbuilder 7
Enterprise Edition juntamente com o SDK Java 1.4. O código está disponível no CD em
anexo ou no site http://www.inf.ufsc.br/~ricardo/magic/.
47
4.4 Testes
O framework para interface gráfica foi usado durante o desenvolvimento do
framework para jogos de cartas e, portanto, as aplicações desenvolvidas sob o segundo
framework serviram para validar o primeiro. A seção 5.4 descreve em detalhes as
aplicações desenvolvidas sob o framework para jogos de carta.
4.5 Documentação
Os diagramas UML apresentados neste capítulo fazem parte da documentação
desenvolvida para o framework descrito. Esses diagramas foram simplificados para
facilitar a leitura e entendimento do documento. A versão completa dos diagramas está
disponível no CD em anexo ou no site http://www.inf.ufsc.br/~ricardo/magic/.
O código fonte é outra forma de documentação. Esse código está comentado com
JavaDocs e o HTML automaticamente gerado está disponível em
http://www.inf.ufsc.br/~ricardo/magic/. Detalhes sobre o código estão descritos na seção
4.3.
5. Framework para Jogos de Cartas
O domínio dos jogos de cartas pode parecer simples à primeira vista, entretanto, é
um domínio vasto e sua análise e generalização exige muita atenção com os detalhes sutis
que poderiam passar despercebidos. A generalização desse domínio em um framework tem
o objetivo didático de fixar os aspectos referentes ao desenvolvimento e utilização de
frameworks.
O framework de jogos de cartas foi criado sob o framework de interface gráfica,
como demonstra a figura abaixo:
48
Figura 24. Aplicação desenvolvida sob o framework de jogos de cartas.
5.1 Análise do Domínio
Analisando várias aplicações10 no domínio de jogos de cartas pode-se chegar a três
entidades básicas:
• Carta – comum a todos as aplicações desse domínio, essa é a entidade básica
de qualquer jogo de cartas.
• Baralho – todas as cartas de um jogo fazem parte de um baralho.
• Conjunto de cartas – a ação mais comum de um jogador num jogo de cartas é
mover uma carta de um conjunto de cartas para outro.
A quantidade de entidades desses tipos, a interação entre essas entidades e a
interação do usuário com essas entidades varia dependendo do jogo.
O diagrama abaixo contém os casos de uso para uma aplicação desenvolvida sob o
framework.
10 Dentre as aplicações estudadas podemos citar: paciência, truco, poker e blackjack.
49
Iniciar jogo
configurar jogo
Jogar
Encerrar jogo
Jogador
Figura 25. Casos de uso para o framework de jogos de cartas.
5.2 Projeto
Procurou-se modelar o framework tendo em vista a generalidade, extensibilidade e
interoperabilidade. O projeto é genérico e pode ser usado como base para o
desenvolvimento de frameworks para jogos de cartas em qualquer linguagem e para
qualquer plataforma.
Hierarquia de Classes – Card
Cartas são o elemento básico de todos os jogos de cartas. Cada jogo possui diferentes
tipos de carta e por isso a classe Card é um hot spot. FourSuitCard é uma implementação
concreta de Card e refere-se à carta comum (com um número e um naipe) usada na maioria
dos jogos. SetOfCards é simplesmente um conjunto de cartas. PileOfCards representa uma
50
das diferentes pilhas encontradas nos jogos, como por exemplo a mão de um jogador ou a
pilha de compra.
FourSuitCard PileOfCards
setAddCondition()setRemoveCondition()
AttributedModel
Model
HOT SPOT
SetOfCardCard **
Figura 26. Hierarquia de modelos presente no framework de jogos de cartas.
PileOfCards
PileOfCards implementa um monte
qualquer dentro de um jogo de cartas. Esse
monte pode ter vários layouts diferentes,
como uma carta sobre a outra, uma ao lado da
outra ou em cascata vertical.
É essa classe que contém as regras do
jogo, dizendo qual carta pode ser movida para
qual lugar através de condições de entrada e
saída de cartas. Essas condições são
implementadas através das classes descritas a
PileOfCardschainRemoval : booleandefaultCardOrientation : booleanLAYOUT_TYPE_HORIZONTAL : int = 2LAYOUT_TYPE_PILE : int = 0LAYOUT_TYPE_VERTICAL : int = 1
addCardTestingCondition()enableConditions()isChainRemovalOn()isDragEnabled()removeAndSaveTestingCondition()removeCardTestingCondition()restoreSavedCard()setAddCondition()setDrag()setRemoveCondition()
51
seguir e seus métodos são invocados sempre que o usuário tenta remover uma carta de
algum PileOfCards e quando ele tenta adicioná-la em outro. Essa classe define também se
a remoção de uma carta do meio do baralho (utilizado em geral com o layout cascata) deve
efetuar a remoção de todas as cartas a partir dessa (para mover uma coluna no jogo
Paciência, por exemplo). Além disso, o método removeAndSaveTestingCondition()
permite a remoção de uma carta ou de um conjunto de cartas mantendo uma referência à
lista removida. Dessa forma as visões são atualizadas, excluindo as cartas removidas, mas
um simples método (restoreSavedCard()) faz com que as cartas sejam adicionadas
novamente no monte na posição em que estavam antes da remoção. Isso facilita, por
exemplo, a implementação de drag and drop, pois permite anular o drag caso o destino não
aceite as cartas sendo movidas.
As condições para entrada e saída de cartas implementam o padrão de projeto
Strategy, permitindo que um algoritmo seja trocado a qualquer hora, porém sem colocar a
implementação de cada tipo de algoritmo dentro da classe que usa ele.
As classes AddCardCondition e RemoveCardCondition possuem métodos que devem
retornar verdadeiro se a alteração (inclusão ou remoção de carta) for aceita e falso caso
contrário. Os parâmetros passados para essas funções contêm informações que possibilitam
a implementação de uma grande quantidade de algoritmos, sendo que a maioria deles
utilizará apenas um subconjunto dos dados fornecidos. Os parâmetros fornecidos diferem
entre AddCardCondition e RemoveCardCondition.
SpecificSuitAddCondition InOrderAddCondition
AddCardCondition
testAdd()testAdd()
CompositeAddCondition
**
HOT SPOT
Figura 27. Hierarquia de condições para adição de cartas em um PileOfCards.
52
HOT SPOT
OnlyLastCardsRemoveCondition TrueRemoveCondition CompositeRemoveCondition
RemoveCardCondition
testRemove()testRemove()
*
Figura 28. Hierarquia de condições para remoção de cartas em um PileOfCards.
Distribuição de Cartas
O diagrama de classes abaixo ilustra o relacionamento entre as classes envolvidas na
distribuição de cartas.
• Um Deck é composto de várias cartas e é a fonte de cartas do distribuidor
• FourSuitDeck é o baralho com 52 cartas de quatro tipos (naipes) diferentes,
usado na maioria dos jogos
• StandardCardDistribuitor é uma implementação concreta de CardDistribuitor
que pega as cartas de um baralho e as divide entre vários SetOfCards (mão
dos jogadores, pilha de compra, etc.)
• CompositeDeck e CompositeCardDistribuitor implementam o padrão de
projeto Composite.
• Deck e CardDistribuitor são hot spots pois o usuário do framework pode
querer criar novos tipos de baralhos e distribuidores com um comportamento
diferente dos existentes.
53
CompositeCardDistributor
addDistributor()removeDistributor()
CardDistributor
distribute()
**
FourSuitDeck
CompositeDeck
addDeck()getNumberOfDecks()removeDeck()
Deck
Deck()initialize()fillDeck()getRandomCard()getNumberOfCards()getCards()
**
StandardCardDistributor
addDestination()addSplitDestination()setSource()
#source
Card**
SetOfCard*
#destinations
*
**
HOT SPOT
HOT SPOT
SpadesSuitDeck
HOT SPOT
Figura 29. Estrutura de classes utilizada para a distribuição de cartas.
Dinamicamente, o processo de distribuição de cartas se comporta da seguinte
maneira:
54
Client : StandardCardDistributor
fiveCardDeck : Deck
hand1 : CompositeCard
hand2 : CompositeCard
remaining : CompositeCard
setSource(aDeck)
addDestination(hand1, 2)
addSplitDestination(remaining)
distribute( ) getRandomCard( )
addCard(card)
getRandomCard( )
addCard(card)
getRandomCard( )
addCard(card)
getRandomCard( )
addCard(card)
getRandomCard( )
addCard(card)
addDestination(hand2, 1)
Figura 30. Modelagem dinâmica da distribuição de cartas.
Criação de um Jogo
A criação do jogo consiste em especializar a classe CardGame implementando os
seguintes métodos abstratos:
• createPiles – inicializar as pilhas do jogos, que devem estar previamente
definidas.
• createPlayers – inicializar os atributos dos jogadores.
• distributeCards – criar o baralho a ser usado no jogo e o distribuidor de
cartas. Distribuir as cartas.
• createInterface – criar a interface para o jogo definido, painéis necessários,
layouts, etc.
55
Figura 31. Classe Básica de um jogo de cartas.
5.3 Implementação
Durante a implementação usou-se o ambiente de desenvolvimento Jbuilder 7
Enterprise Edition juntamente com o SDK Java 1.4. O código está disponível no CD em
anexo ou no site http://www.inf.ufsc.br/~ricardo/magic/
5.4 Testes
Aplicações teste foram desenvolvidas para avaliar a capacidade do framework de
facilitar o desenvolvimento de aplicações no domínio proposto. Além de validar o
framework para jogos de cartas, as aplicações teste serviram também para validar o
framework para interface gráfica.
FreeCell
O FreeCell é um jogo de cartas simples e muito popular. O objetivo do jogo é
ordenar as cartas em quatro pilhas, uma para cada naipe. Existem três tipos de pilhas de
carta nesse jogo:
• Destinos – existem quatro pilhas desse tipo, uma para cada naipe. Todas as
cartas devem estar em um destino para que o jogo acabe.
o Condição de entrada – as cartas devem ser adicionadas em ordem
crescente e todas as cartas de um destino devem ser do mesmo naipe.
o Condição de saída – depois de adicionada em um destino, uma carta
não pode ser retirada.
• Espaços – essas pilhas são usadas para armazenar temporariamente uma carta
56
durante o jogo. O número de espaços é configurável.
o Condição de entrada – qualquer carta pode ser adicionada em um
espaço, independentemente de seu naipe ou número. Entretanto, cada
espaço comporta no máximo uma carta.
o Condição de saída – uma carta sempre pode ser retirada de um espaço.
• Pilhas de jogo – essas pilhas são usadas para movimentar as cartas durante o
jogo. O número de pilhas desse tipo é configurável.
o Condição de entrada – as cartas devem ser adicionadas em ordem
decrescente. A carta adicionada deve ser de cor diferente da última
carta na pilha.
o Condição de saída – somente a última carta da pilha pode ser retirada.
Inicialmente, as cartas do baralho são distribuídas de forma aleatória entra as pilhas
de jogo.
Figura 32. Screenshot do jogo FreeCell.
Paciência
Esse é o jogo mais jogado no mundo. Assim como no FreeCell, o objetivo desse jogo
Espaços Destinos
Pilhas de jogo
57
é ordenar as cartas por naipe. Existem três tipos de pilhas de carta nesse jogo:
• Pilha de compra – essa pilha armazena as carta que ainda não entraram no
jogo.
o Condição de entrada – nenhuma carta pode ser adicionada nessa pilha.
o Condição de saída – uma carta sempre pode ser retirada dessa pilha.
• Destinos – existem quatro pilhas desse tipo, uma para cada naipe. Todas as
cartas devem estar em um destino para que o jogo acabe.
o Condição de entrada – as cartas devem ser adicionadas em ordem
crescente e todas as cartas de um destino devem ser do mesmo naipe.
o Condição de saída – a carta do topo de uma pilha desse tipo sempre
pode ser removida.
• Pilhas de jogo – essas pilhas são usadas para movimentar as cartas durante o
jogo. O número de pilhas desse tipo é configurável.
o Condição de entrada – as cartas devem ser adicionadas em ordem
decrescente. A carta adicionada deve ser de cor diferente da última
carta na pilha.
o Condição de saída – um conjunto de cartas pode ser retirado do final
dessa pilha desde que elas estejam em ordem decrescente e sejam de
cores intercaladas.
As cartas são distribuídas entre as pilhas de jogo, uma para a primeira pilha, duas
para a segunda, etc. As cartas restantes são adicionadas na pilha de compra.
58
Figura 33. Screenshot do jogo Paciência.
5.5 Documentação
A documentação do framework para jogos de carta foi elaborada de maneira similar
à documentação do framework para interface gráfica. A seção 4.5 descreve em detalhes as
fontes de documentação.
Pilha de compra Destinos
Pilhas de jogo
59
6. Conclusões
A estrutura de classes de um framework, bem como o modelo de colaboração entre
essas classes, é bastante complexa. Projetar e implementar essa estrutura exige um alto
nível de conhecimento das técnicas e ferramentas para projeto e desenvolvimento OO. Por
essa razão, desenvolver um framework abrangente e extensível é um ótimo exercício
dessas técnicas.
Além disso, o desenvolvimento de aplicações sob um framework explicita a
importância do reuso. Um framework bem abrangente facilita muito o desenvolvimento de
aplicações no domínio tratado.
Entretanto, o desenvolvimento de um framework exige um esforço bem maior do que
o desprendido para criar uma aplicação isolada. Uma análise detalhada do domínio alvo e
da relação custo beneficio do desenvolvimento de um framework deve fazer parte da
análise de requisitos de um projeto qualquer que tencione criar um framework para um
domínio específico.
Os padrões de projeto, utilizados durante a modelagem e implementação dos
frameworks descritos nesse documento, são referências importantíssimas no
desenvolvimento de aplicações OO. Além de facilitar o projeto e implementação, os
padrões são uma boa fonte de documentação para a interação entre classes de um
subsistema de uma aplicação OO qualquer.
60
7. Sugestões de Trabalhos Futuros
Apesar de facilitar o desenvolvimento de aplicações no domínio tratado, o uso de
frameworks exige um certo conhecimento técnico. É necessário conhecer técnicas de
projeto e desenvolvimento para criar aplicações sob um framework. É difícil para um
usuário leigo que queira desenvolver aplicações entender a estrutura de classes de um
framework e até mesmo a documentação que geralmente é disponibilizada. De forma a
facilitar o uso dos frameworks desenvolvidos, seria de grande valia a criação de
cookbooks que explicassem com detalhes como criar aplicações para cada um dos
frameworks desenvolvidos.
Além disso, a criação de um ambiente gráfico para o desenvolvimento de aplicações
possibilitaria que um maior número de pessoas se beneficiassem das vantagens do uso de
um framework. Existem diversos ambientes para a criação de interfaces gráficas nas mais
diferentes linguagens. O IDE JBuilder por exemplo, disponibiliza um ambiente para
facilitar o uso do framework AWT do Java. Criar um ambiente para os frameworks
propostos não seria uma tarefa simples, entretanto este é um requisito para que os
framework se popularizem.
Sabemos, também, que o ciclo de vida de um framework é retroativo. À medida que
novas aplicações são desenvolvidas surgem características do domínio que não foram
cobertas pelo framework. Algumas oportunidades de refinamento foram descobertas
durante a validação, isto é, criação de aplicações, dos frameworks propostos:
Framework para Interface Gráfica
• Adicionar suporte a elementos básicos de uma GUI – elementos como botões,
caixas de texto, menus, barras de rolagem, etc. são importantes no
desenvolvimento de qualquer aplicação sob um framework para GUI.
• Adicionar suporte a transparência na classe View – a possibilidade de criar
visões transparentes melhoraria o desempenho do processo de desenho além
de abrir mais uma opção para que o desenvolvedor de aplicações crie visões
para seus modelos.
61
Framework para Jogos de Cartas
• Adicionar suporte a rede – a possibilidade de jogar em rede é uma
característica que está cada vez mais presente nos jogos de cartas. Criar uma
estrutura que suporte essa característica é imprescindível.
• Adicionar uma estrutura para gerencias turnos – vários jogos de cartas que
envolvem mais de um jogador são baseados em turnos. Cada jogador joga no
seu turno e geralmente não podem efetuar ações no turno dos adversários.
Essa é uma característica importante ainda não coberta pelo framework para
jogos de carta proposto.
62
8. Referências Bibliográficas
[DOU 99] DOUGLASS, B. P. Doing Hard Time: Developing Real-Time Systems with
UML, Objects, Frameworks, and Patterns. [s.l.]: Addison Weasley, 1999.
[FAY 99] FAYAD, M. et al. Building Application Frameworks. New York: Wiley,
1999.
[GAM 99] GAMMA, E. Design patterns: elements of reusable object-oriented software.
Reading: Addison Wesley, 1994.
[LEW 95] LEWIS, T. et al. Object-oriented application frameworks. Greenwich:
Manning, 1995.
[MEY 97] MEYER, B. Object-oriented software construction. 2 ed. [s.l.]: Prentice Hall
PTR, 1997.
[SIL 98] SILVA, R. P.; PRICE, R. T. A busca de generalidade, flexibilidade e
extensibilidade no processo de desenvolvimento de frameworks
orientados a objetos. In: WORKSHOP IBEROAMERICANO DE
ENGENHARIA DE REQUISITOS E AMBIENTES DE SOFTWARE,
(IDEAS), 1998, Torres. Anais... Porto Alegre: Instituto de Informática /
UFRGS, 1998. v.2, p298-309.
[SIL 00] SILVA, R. P. Suporte ao desenvolvimento e uso de frameworks e
componentes. Porto Alegre: Instituto de Informática / UFRGS, 2000.
63
9. Apêndices
9.1 Guia para a criação de aplicações
Hot Spots – Framework de interface gráfica
A seguir estão expostos os hot spots identificados nos diagramas do framework de
interface gráfica.
Para a criação de um novo modelo o usuário do framework pode utilizar como super
classe qualquer elemento da hierarquia de classes Model. Deve-se tomar o cuidado de, a
cada mudança efetuada no modelo, seja por chamada de seus métodos ou em decorrência
de algum evento interno, chamar a função notifyViewers() para permitir que as visões
sejam atualizadas. Deve-se também tomar o cuidado de garantir que o estado do objeto
seja consistente no momento da chamada, pois nesse momento qualquer método pode ser
chamado sem o controle explícito do modelo. É recomendado que o objeto changeInfo seja
utilizado para informar de forma precisa qual mudança que ocorreu.
Model
notifyViewers(changeInfo : Object) : void
Existem dois casos em que uma nova View deve ser criada. O primeiro ocorre
quando um novo modelo é criado e se deseja mostrá-lo de forma gráfica; o segundo,
quando se deseja criar um novo tipo de visualização para um modelo já existente. Ambos
são equivalentes no passo de criação da View e diferem apenas na definição do
ViewFactory que será usado.
Existem 4 métodos que precisam ser redefinidos numa nova visão:
• isCompatible(): deve verificar se o modelo a ser utilizado por uma View é
compatível com esta.
• modelChanged(): deve atualizar qualquer dado sobre o modelo que é mantido
dentro da View.
64
• alwaysRepaintAll(): retorna um valor booleano que identifica se uma
operação de desenho (drawArea()) pode ser efetuada apenas em parte do
objeto ou se o mecanismo de desenho implementado suporta apenas o
redesenho de toda a área. Possui implementação padrão que retorna true. Só
deve ser mudado por razões de desempenho.
• drawArea(): responsável por desenhar a visão. Possui um parâmetro que
identifica qual área deve ser desenhada (será sempre a área total do objeto se
alwaysRepaintAll() retornar verdadeiro).
• calculatePreferredSize(): deve retornar o tamanho preferido para as
dimensões do objeto. Esse valor é usado pelos gerenciadores de disposição
como referência e é recalculado sempre que o modelo muda, reorganizando o
layout quando o valor mudar.
View
alwaysRepaintAll()calculatePreferredSize()drawArea()modelChanged()isCompatible()
Após a criação da visão, os ViewFactorys devem ser atualizados para criarem visões
do novo tipo. No caso de uma visão para um modelo novo, o DefaultViewFactory deve ser
atualizado11 para incluir o novo tipo. Caso a visão feita seja para um modelo já existente e
que já possua uma View, o DefaultViewFactory deve ser atualizado caso a nova visão
passe a ser a padrão ou um novo ViewFactory deve ser criado para ser usado em um
contexto específico (como um painel dentro da interface).
ViewFactory
createViewFor(model : Model) : View
Aplicações em geral utilizarão a classe PanelModel como forma de colocar modelos
de forma hierárquica dentro da interface com o usuário. Um dos itens configuráveis neste
11 Veja documentação no JavaDocs para mais detalhes.
65
caso é qual gerenciador de disposição será usado. Vários gerenciadores já estão pré-
definidos, porém, o usuário pode definir novos, se necessário, criando uma subclasse de
LayoutManager.
Para isso o método doLayout() deve ser implementado, atribuindo para cada View do
painel sua nova posição e tamanho. Esse método deve calcular as novas posições
baseando-se apenas no tamanho do painel e no valor retornado por getPreferredSize() de
cada View. Também deve ser redefinido o método getPreferredSize() no layout, retornando
as dimensões necessárias para acomodar as visões dentro do painel da melhor forma
possível.
LayoutManager
doLayout()getPreferedSize()
Hot Spots – Framework de interface gráfica
A seguir estão expostos os hot spots identificados nos diagramas do framework de
jogos de cartas.
Diferentes jogos de cartas podem usar cartas de tipos diferentes. Caso isso seja
necessário, uma subclasse de Card deve ser implementada, juntamente com uma View
específica para esse tipo de carta. Card é uma subclasse de AttributedModel, logo todo
atributo a ser implementado pela classe deve ser adicionado utilizando o método
addAttribute(). Deve ser definido também qual modelo representa o fundo da carta. Em
geral esse modelo restringe-se a uma figura, porém itens mais complexos podem ser
utilizados.
Card
addAttribute()getAttribute()getBackModel()
Cada jogo pode utilizar um subconjunto das possibilidades possíveis em um
66
determinado tipo de carta (apenas cartas de copas, por exemplo) e portanto Decks
específicos podem ser criados para um determinado jogo. A criação de um novo Deck
consiste apenas na criação de uma subclasse de Deck que implementa o método fillDeck()
adicionando todas as cartas pertencentes na lista interna.
Deck
fillDeck()
Também pode ser necessária a definição de um novo tipo de distribuição de cartas
para um jogo específico. Nada é assumido com relação à fonte dessas cartas nem qual o
destino delas, por isso apenas o método distribute() precisa ser implementado. Porém é
esperado que uma implementação de CardDistributor utilize Decks como fonte e
SetOfCards como destino, de forma a manter compatibilidade com o resto da estrutura.
CardDistributor
distribute()
A classe PileOfCards utiliza condições para a entrada e para a saída de cartas. Essas
condições são utilizadas para implementar as regras em um jogo de cartas.Os dois métodos
disponíveis devem ser implementados em uma subclasse. O primeiro corresponde apenas à
tentativa de adição/remoção de uma única carta e o segundo a adição/remoção de um
conjunto seqüencial de cartas. Os parâmetros fornecidos podem ser utilizados para obter
mais informações antes de decidir pelo resultado do teste.
AddCardCondition
testAdd(card : Card, source : PileOfCards, destinationIndex : int, destination : PileOfCards) : booleantestAdd(cards : PileOfCards, source : PileOfCards, destinationStartIndex : int, destination : PileOfCards) : boolean
RemoveCardCondition
testRemove(card : Card, sourceIndex : int, source : PileOfCards) : booleantestRemove(cards : PileOfCards, sourceStartIndex : int, source : PileOfCards) : boolean
67
10. Anexos
10.1 Código Fonte
O código fonte do framework está disponível no CD em anexo. O código fonte das
aplicações desenvolvidas está exposto abaixo.
Paciência
import conditions.add.*; import conditions.remove.*; import java.awt.Rectangle; public class Solitaire extends CardGame { protected class InOrderSpecificSuitAddCondition extends CompositeAddCondition { public InOrderSpecificSuitAddCondition() { addCondition(new InOrderAddCondition()); addCondition(new SpecificSuitAddCondition()); } public InOrderSpecificSuitAddCondition(int allowedSuit) { addCondition(new InOrderAddCondition()); addCondition(new SpecificSuitAddCondition(allowedSuit)); } } protected class ToggleColorInOrderAddCondition extends CompositeAddCondition { public ToggleColorInOrderAddCondition() { addCondition(new InOrderAddCondition(false)); addCondition(new ToggleColorAddCondition()); } } protected class ToggleColorInOrderOnlyLastCardsRemoveCondition extends CompositeRemoveCondition { public ToggleColorInOrderOnlyLastCardsRemoveCondition() { addCondition(new InOrderRemoveCondition(false)); addCondition(new OnlyLastCardsRemoveCondition()); addCondition(new ToggleColorRemoveCondition()); addCondition(new OnlyFaceUpRemoveCondition()); } }
68
public class DrawPileEventHandler extends CommonMouseEventHandler { PileOfCards pileOfCards; public DrawPileEventHandler (PileOfCards pileOfCards) { this.pileOfCards = pileOfCards; } protected void mouseDown(View sender, MouseEvent event) { } protected void mouseUp(View sender, MouseEvent event) { // empty } protected void mouseClick(View sender, MouseEvent event) { Card card = pileOfCards.getLastCard(); pileOfCards.removeLastCard(); pileOfCards.addCard(0, card); } protected void mouseDoubleClick(View sender, MouseEvent event) { mouseClick(sender, event); } protected void mouseMove(View sender, MouseEvent event) { // empty } protected void mouseEnter(View sender, MouseEvent event) { // empty } protected void mouseExit(View sender, MouseEvent event) { // empty } } public class GamePileEventHandler extends CommonMouseEventHandler { PileOfCards pileOfCards; public GamePileEventHandler(PileOfCards pileOfCards) { this.pileOfCards = pileOfCards; } protected void mouseDown(View sender, MouseEvent event) { } protected void mouseUp(View sender, MouseEvent event) {
69
// empty } protected void mouseClick(View sender, MouseEvent event) { if (sender.getModel() == pileOfCards.getLastCard()) { Card card = pileOfCards.getLastCard(); card.setFaceUp(true); } } protected void mouseDoubleClick(View sender, MouseEvent event) { mouseClick(sender, event); } protected void mouseMove(View sender, MouseEvent event) { // empty } protected void mouseEnter(View sender, MouseEvent event) { // empty } protected void mouseExit(View sender, MouseEvent event) { // empty } } public static void main(String args[]) { Solitaire game = new Solitaire(7); game.initialize(); } protected PileOfCards drawPile; protected PileOfCards destinations[] = new PileOfCards[4]; protected int numberOfGamePiles; protected PileOfCards gamePiles[]; public Solitaire(int numberOfGamePiles) { this.numberOfGamePiles = numberOfGamePiles; } public void createInterface() { // a panel for the destination piles PanelModel destinationsPanel = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_HORIZONTAL, 20, SpacedLayout.ALIGN_LEFT, false, false, false)); for (int i = 0; i < destinations.length; i++) { destinationsPanel.addModel(destinations[i], null);
70
} // and one for the game piles PanelModel piles = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_HORIZONTAL, 20, SpacedLayout.ALIGN_LEFT, false, false, false)); for (int i = 0; i < gamePiles.length; i++) { piles.addModel(gamePiles[i], null); } // a panel to group the draw pile and the destination piles PanelModel topPanel = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_HORIZONTAL, 260, SpacedLayout.ALIGN_TOP, false, false, false)); topPanel.addModel(drawPile, null); topPanel.addModel(destinationsPanel, null); // our surface PanelModel surface = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_VERTICAL, 10, SpacedLayout.ALIGN_TOP, false, false, false)); surface.addModel(topPanel, null); surface.addModel(piles, null); // and, finally, our frame Frame frame = new Frame(surface, "Paciência"); frame.setBounds(new Rectangle(0, 0, 850, 400)); frame.centerInScreen(); frame.setVisible(true); } public void distributeCards() { //Deck mainDeck = new FourSuitDeck("D:/JStuff/CARDDECK/CARDS-DE"); Deck mainDeck = new FourSuitDeck("solitaire"); StandardCardDistributor cardDistributor = new StandardCardDistributor(); cardDistributor.setSource(mainDeck); for (int i = 0; i < gamePiles.length; i++) { cardDistributor.addDestination(gamePiles[i], i, false); cardDistributor.addDestination(gamePiles[i], 1, true); } cardDistributor.addSplitDestination(drawPile); try { cardDistributor.distribute(); } catch (NotEnoughCardsException exp) {
71
System.err.println("Error distributing card."); } } public void createPiles() { // card holders conditions OnlyLastCardsRemoveCondition drawPileRemoveCondition = new OnlyLastCardsRemoveCondition(); FalseAddCondition drawPileAddCondition = new FalseAddCondition(); // creating our draw pile drawPile = new PileOfCards(PileOfCards.LAYOUT_TYPE_PILE, true); drawPile.setAddCondition(drawPileAddCondition); drawPile.setRemoveCondition(drawPileRemoveCondition); drawPile.setDefaultEventHandler(new DrawPileEventHandler(drawPile)); // destinations remove condition // we have to create a new add condition for each destination OnlyLastCardsRemoveCondition destinationsRemoveCondition = new OnlyLastCardsRemoveCondition(); // our destinations for (int i = 0; i < destinations.length; i++) { destinations[i] = new PileOfCards(PileOfCards.LAYOUT_TYPE_PILE, true); destinations[i].setAddCondition(new InOrderSpecificSuitAddCondition()); destinations[i].setRemoveCondition(destinationsRemoveCondition); } ToggleColorInOrderAddCondition gamePilesAddCondition = new ToggleColorInOrderAddCondition(); ToggleColorInOrderOnlyLastCardsRemoveCondition gamePilesRemoveCondition = new ToggleColorInOrderOnlyLastCardsRemoveCondition(); // and our game piles gamePiles = new PileOfCards[numberOfGamePiles]; for (int i = 0; i < gamePiles.length; i++) { gamePiles[i] = new PileOfCards(PileOfCards.LAYOUT_TYPE_VERTICAL, false); gamePiles[i].setAddCondition(gamePilesAddCondition); gamePiles[i].setRemoveCondition(gamePilesRemoveCondition); gamePiles[i].setChainRemoval(true); gamePiles[i].setDefaultEventHandler(new GamePileEventHandler(gamePiles[i])); } } public void createPlayers() { // empty }
Paciência
72
import conditions.add.*; import conditions.remove.*; import java.awt.Rectangle; public class FreeCell extends CardGame { protected class InOrderSpecificSuitAddCondition extends CompositeAddCondition { public InOrderSpecificSuitAddCondition() { addCondition(new InOrderAddCondition()); addCondition(new SpecificSuitAddCondition()); } public InOrderSpecificSuitAddCondition(int allowedSuit) { addCondition(new InOrderAddCondition()); addCondition(new SpecificSuitAddCondition(allowedSuit)); } } protected class ToggleColorInOrderAddCondition extends CompositeAddCondition { public ToggleColorInOrderAddCondition() { addCondition(new InOrderAddCondition(false)); addCondition(new ToggleColorAddCondition()); } } public static void main(String args[]) { FreeCell game = new FreeCell(8, 4); game.initialize(); } protected int numberOfCardHolders; protected PileOfCards cardHolders[]; protected PileOfCards destinations[] = new PileOfCards[4]; protected int numberOfGamePiles; protected PileOfCards gamePiles[]; public FreeCell(int numberOfGamePiles, int numberOfCardHolders) { this.numberOfGamePiles = numberOfGamePiles; this.numberOfCardHolders = numberOfCardHolders; } public void createInterface() { // a panel for the destination piles PanelModel destinationsPanel = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_HORIZONTAL, 10, SpacedLayout.ALIGN_TOP, false, false, false));
73
for (int i = 0; i < destinations.length; i++) { destinationsPanel.addModel(destinations[i], null); } // another one for the card holders PanelModel holdersPanel = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_HORIZONTAL, 10, SpacedLayout.ALIGN_TOP, false, false, false)); for (int i = 0; i < cardHolders.length; i++) { holdersPanel.addModel(cardHolders[i], null); } // and one for the game piles PanelModel piles = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_HORIZONTAL, 10, SpacedLayout.ALIGN_TOP, false, false, false)); for (int i = 0; i < gamePiles.length; i++) { piles.addModel(gamePiles[i], null); } // a panel to group the card holders and the destination piles PanelModel topPanel = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_HORIZONTAL, 90, SpacedLayout.ALIGN_LEFT, false, false, false)); topPanel.addModel(holdersPanel, null); topPanel.addModel(destinationsPanel, null); // our surface PanelModel surface = new PanelModel(new SpacedLayout(SpacedLayout.ORIENTATION_VERTICAL, 20, SpacedLayout.ALIGN_MIDDLE, false, false, false)); surface.addModel(topPanel, null); surface.addModel(piles, null); // and, finally, our frame Frame frame = new Frame(surface, "FreeCell"); frame.setBounds(new Rectangle(0, 0, 850, 400)); frame.centerInScreen(); frame.setVisible(true); } public void distributeCards() { Deck mainDeck = new FourSuitDeck("freecell"); StandardCardDistributor cardDistributor = new StandardCardDistributor(); cardDistributor.setSource(mainDeck); for (int i = 0; i < gamePiles.length; i++) {
74
cardDistributor.addSplitDestination(gamePiles[i]); } try { cardDistributor.distribute(); } catch (NotEnoughCardsException exp) { System.err.println("Error distributing card."); } } public void createPiles() { // card holders conditions TrueRemoveCondition holdersRemoveCondition = new TrueRemoveCondition(); MaxNumberOfCardsAddCondition holdersAddCondition = new MaxNumberOfCardsAddCondition(1); // creating our card holders cardHolders = new PileOfCards[numberOfCardHolders]; for (int i = 0; i < cardHolders.length; i++) { cardHolders[i] = new PileOfCards(PileOfCards.LAYOUT_TYPE_PILE, true); cardHolders[i].setAddCondition(holdersAddCondition); cardHolders[i].setRemoveCondition(holdersRemoveCondition); } // destinations remove condition // we have to create a new add condition for each destination FalseRemoveCondition destinationsRemoveCondition = new FalseRemoveCondition(); // our destinations for (int i = 0; i < destinations.length; i++) { destinations[i] = new PileOfCards(PileOfCards.LAYOUT_TYPE_PILE, true); destinations[i].setAddCondition(new InOrderSpecificSuitAddCondition()); destinations[i].setRemoveCondition(destinationsRemoveCondition); } ToggleColorInOrderAddCondition gamePilesAddCondition = new ToggleColorInOrderAddCondition(); OnlyLastCardsRemoveCondition gamePilesRemoveCondition = new OnlyLastCardsRemoveCondition(); // and our game piles gamePiles = new PileOfCards[numberOfGamePiles]; for (int i = 0; i < gamePiles.length; i++) { gamePiles[i] = new PileOfCards(PileOfCards.LAYOUT_TYPE_VERTICAL, true); gamePiles[i].setAddCondition(gamePilesAddCondition); gamePiles[i].setRemoveCondition(gamePilesRemoveCondition); // gamePiles[i].setRemoveCondition(new TrueRemoveCondition()); gamePiles[i].setChainRemoval(false);
75
} } public void createPlayers() { // empty } }
76
Artigo
Recommended