"CADA PADRÃO DESCREVE UM PROBLEMA QUE OCORRE FREQUENTEMENTE E ENTÃO DESCREVE O CERNE DA SOLUÇÃO AO PROBLEMA DE FORMA A PODER USAR A SOLUÇÃO MILHÕES DE VEZES EM SITUAÇÕES SEMELHANTES"
CONTÊM O SOMATÓRIO DA EXPERIÊNCIA DOS MELHORES PROJETISTAS O-O!
ESTÃO REVOLUCIONANDO O PROJETO DE SOFTWARE O-O DESDE 1994 QUANDO O FAMOSO LIVRO DA "GANG OF FOUR" (GoF) APARECEU (VER BIBLIOGRAFIA)
ELEMENTOS ESSENCIAIS DE UM PADRÃO:
UM NOME
DESCREVE O PROBLEMA DE PROJETO, SUAS SOLUÇÕES E CONSEQUÊNCIAS EM POUCAS PALAVRAS
PERMITE PROJETAR NUM NÍVEL MAIS ALTO DE ABSTRAÇÃO
PERMITE FALAR COM OUTROS SOBRE SOLUÇÕES E DOCUMENTAR CÓDIGO, JÁ QUE OS NOMES DE PADRÕES ESTÃO FICANDO PADRONIZADOS
EQUIVALENTE A PADRONIZAR "LISTA ENCADEADA", "PILHA", ETC. NO MUNDO DAS ESTRUTURAS DE DADOS
O PROBLEMA
DESCREVE QUANDO APLICAR O PADRÃO
DESCREVE O PROBLEMA E O CONTEXTO
PODE DESCREVER PROBLEMAS ESPECÍFICOS DE PROJETO (EX. COMO REPRESENTAR ALGORITMOS COMO OBJETOS)
PODE DESCREVER ESTRUTURAS DE OBJETOS OU DE CLASSES QUE SÃO SINTOMAS DE UM PROJETO INFLEXÍVEL
AS VEZES, O PADRÃO LISTA CONDIÇÕES QUE DEVEM SE APLICAR PARA USAR O PADRÃO
A SOLUÇÃO
DESCREVE OS ELEMENTOS CONSTITUINTES DO PROJETO, SEUS RELACIONAMENTOS, RESPONSABILIDADES E COLABORAÇÕES
A SOLUÇÃO NÃO DESCREVE UM PROJETO OU IMPLEMENTAÇÃO CONCRETOS PORQUE UM PADRÃO É UM GABARITO DE SOLUÇÃO PARA VÁRIAS SITUAÇÕES
AS CONSEQUÊNCIAS
OS RESULTADOS E TRADE-OFFS DA APLICAÇÃO DO PADRÃO
DIZ RESPEITO A TRADE-OFFS DE ESPAÇO, TEMPO, FLEXIBILIDADE, EXTENSIBILIDADE, PORTABILIDADE
AJUDAM A DEIXAR O SISTEMA INDEPENDENTE DE COMO SEUS OBJETOS SÃO CRIADOS, COMPOSTOS E REPRESENTADOS
SÃO DOIS TIPOS:
PADRÕES DE CRIAÇÃO VIA CLASSES
USAM HERANÇA PARA VARIAR A CLASSE QUE É INSTANCIADA
EXEMPLO: FACTORY METHOD
PADRÕES DE CRIAÇÃO VIA OBJETOS
DELEGAM A INSTANCIAÇÃO PARA OUTRO OBJETO
EXEMPLO: ABSTRACT FACTORY
TAIS PADRÕES SÃO IMPORTANTES JÁ QUE A COMPOSIÇÃO É USADA MAIS QUE HERANÇA PARA ESTENDER FUNCIONALIDADE
EM VEZ DE CODIFICAR UM COMPORTAMENTO ESTATICAMENTE, DEFINIMOS PEQUENOS COMPORTAMENTOS PADRÃO E USAMOS COMPOSIÇÃO PARA DEFINIR COMPORTAMENTOS MAIS COMPLEXOS
OS PADRÕES DE CRIAÇÃO DISCUTEM TEMAS RECORRENTES:
ELES ENCAPSULAM O CONHECIMENTO DAS CLASSES CONCRETAS QUE SÃO INSTANCIADAS
LEMBRE QUE PREFERIMOS NOS "AMARRAR" A INTERFACES (VIA INTERFACE OU CLASSES ABSTRATAS) DO QUE A CLASSES CONCRETAS
ISSO PROMOVE A FLEXIBILIDADE DE MUDANÇA
MAS EU NÃO POSSO CRIAR UM OBJETO SABENDO APENAS A INTERFACE!!
PADRÕES DE CRIAÇÃO RESOLVEM ESTA SINUCA
ELES ESCONDEM COMO INSTÂNCIAS DAS CLASSES CONCRETAS SÃO INSTANCIADAS
USAREMOS A CONSTRUÇÃO DE LABIRINTOS PARA UM JOGO VIA COMPUTADOR PARA MOSTRAR ALGUNS PADRÕES DE CRIAÇÃO
IGNORAREMOS MUITOS DETALHES DO LABIRINTO (O QUE PODE ESTAR NO LABIRINTO, OS JOGADORES, ETC.)
FOCO NA CRIAÇÃO DOS LABIRINTOS
UM LABIRINTO É UM CONJUNTO DE SALAS
UMA SALA CONHECE SEUS QUATRO VIZINHOS
VIZINHOS PODEM SER OUTRA SALA, UMA PAREDE OU UMA PORTA PARA OUTRA SALA
AS CLASSES IMPORTANTES SÃO Sala, Porta E Parede
SÓ TRATAREMOS AS PARTES DAS CLASSES QUE INTERESSAM PARA A CRIAÇÃO DO LABIRINTO
O DIAGRAMA DE CLASSES SEGUE ABAIXO
EXERCÍCIO PARA CASA: REDESENHAR O DIAGRAMA COM INTERFACES
UM DIAGRAMA DE OBJETOS SEGUE PARA UM PEQUENO LABIRINTO
CADA SALA TEM QUATRO VIZINHOS
USAMOS NORTE, SUL, LESTE, OESTE PARA REFERENCIÁ-LOS
A CLASSE ElementoDoLabirinto É A CLASSE ABSTRATA COMUM PARA TODOS OS COMPONENTES DE UM LABIRINTO
TEM UM MÉTODO (POLIMÓRFICO) Entra CUJO SIGNIFICADO DEPENDE ONDE ESTÁ ENTRANDO
SE FOR UMA SALA, A LOCALIZAÇÃO DO JOGADOR MUDA
SE FOR UMA PORTA ABERTA, VOCÊ VAI PARA OUTRA SALA, CASO CONTRÁRIO SE MACHUCA
SE FOR UMA PAREDE, VOCÊ SE MACHUCA
abstract class ElementoDeLabirinto { public abstract void entra(); }
EXEMPLO: SE VOCÊ ESTIVER NUMA SALA E QUISER IMPLEMENTAR A OPERAÇÃO "VÁ PARA O LESTE", O JOGO DETERMINA QUAL ElementoDoLabirinto ESTÁ DO LADO LESTE E CHAMA Entra() DESTE OBJETO
A MÉTODO Entra DA SUBCLASSE ESPECÍFICA DETERMINA O QUE OCORRE
NUM JOGO REAL, Entra PODERIA ACEITAR O OBJETO JOGADOR COMO PARÂMETRO
Sala É A SUBCLASSE CONCRETA DE ElementoDoLabirinto QUE DEFINE AS RELAÇÕES-CHAVE ENTRE OBJETOS
MANTÉM REFERÊNCIAS PARA 4 OUTROS ElementoDoLabirinto
ARMAZENA UM NÚMERO DE SALA PARA INDENTIFICAR AS SALAS DO LABIRINTO
class Sala extends ElementoDeLabirinto { private ElementoDeVizinho[] vizinhos = new ElementoDeVizinho[4]; private int númeroDaSala; public void Sala(int númeroDaSala) { ... } public void entra() { ... } public ElementoDeLabirinto getVizinho(int direção) { ... } public void setVizinho(int direção, ElementoDeLabirinto vizinho) { ... } } class Parede extends ElementoDeLabirinto { public void Parede() { ... } public void entra() { ... } } class Porta extends ElementoDeLabirinto { private Sala sala1, sala2; private boolean estáAberta; public void Porta(ElementoDeLabirinto sala1, ElementoDeLabirinto sala2) { ... } public void entra() { ... } public Sala salaDoOutroLado(Sala sala) { ... } }
TAMBÉM PRECISAMOS DE UM Labirinto PARA REPRESENTAR UMA COLEÇÃO DE SALAS
A CLASSE Labirinto PODE LOCALIZAR UMA SALA DADO SEU NÚMERO COM O MÉTODO getSala()
class Labirinto { private Vector salas = new Vector(); public void Labirinto() { ... } public void adicionaSala(Sala sala) { ... } public Sala getSala(int númeroDaSala) { ... } }
TAMBÉM DEFINIMOS UMA CLASSE Jogo QUE CRIA O LABIRINTO
UMA FORMA SIMPLES DE CRIAR UM LABIRINTO É DE CRIAR OS COMPONENTES, ADICIONÁ-LOS AO LABIRINTO E INTERCONECTÁ-LOS
EXEMPLO DA CRIAÇÃO DE UM LABIRINTO COM 2 SALAS E UMA PORTA ENTRE ELAS
class Jogo { ... public Labirinto criaLabirinto() { Labirinto umLabirinto = new Labirinto(); Sala sala1 = new Sala(1); Sala sala2 = new Sala(2); Porta aPorta = new Porta(sala1, sala2); umLabirinto.adicionaSala(sala1); umLabirinto.adicionaSala(sala2); sala1.setVizinho(NORTE, new Parede()); sala1.setVizinho(LESTE, aPorta); sala1.setVizinho(SUL, new Parede()); sala1.setVizinho(OESTE, new Parede()); sala2.setVizinho(NORTE, new Parede()); sala2.setVizinho(LESTE, new Parede()); sala2.setVizinho(SUL, new Parede()); sala2.setVizinho(OESTE, aPorta); return umLabirinto; } ... }
O PROBLEMA DESTA SOLUÇÃO É SUA INFLEXIBILIDADE
EM PARTICULAR, VEREMOS COMO MUDAR O PROJETO PARA CRIAR DIFERENTES TIPOS DE LABIRINTOS
LABIRINTOS ENCANTADOS
COM PORTAS TRAVADAS QUE PRECISAM DE UM ENCANTAMENTO PARA ABRIR
SALAS CONTENDO ENCANTAMENTOS QUE PODEM SER APANHADOS
LABIRINTOS PERIGOSOS
SALAS COM BOMBAS QUE PODEM SER EXPLODIDAS PARA DANIFICAR AS PAREDES (E TALVEZ O JOGADOR!)
COMO MUDAR criaLabirinto PARA FACILMENTE CRIAR ESTES NOVOS TIPOS DE LABIRINTOS?
O MAIOR PROBLEMA É QUE A SOLUÇÃO ATUAL NOS FORÇA A COLOCAR EM CÓDIGO AS CLASSES CONCRETAS QUE SERÃO INSTANCIADAS
USAREMOS PADRÕES DE CRIAÇÃO PARA TORNAR O PROJETO MAIS FLEXÍVEL
DEFINIR UMA INTERFACE PARA CRIAR OBJETOS DE FORMA A DEIXAR SUBCLASSES DECIDIREM QUAL CLASSE INSTANCIAR
FACTORY METHOD DEIXA QUE SUBCLASSES FAÇAM A INSTANCIAÇÃO
TAMBÉM CHAMADO DE "CONSTRUTOR VIRTUAL"
A IDÉIA É SIMPLES: EM VEZ DE UM CLIENTE QUE PRECISA DE UM OBJETO CHAMAR new E ASSIM ESPECIFICAR A CLASSE CONCRETA QUE ELE INSTANCIA, O CLIENTE CHAMA UM MÉTODO ABSTRATO (FACTORY METHOD) ESPECIFICADO EM ALGUMA CLASSE ABSTRATA (OU INTERFACE) E A SUBCLASSE CONCRETA VAI DECIDIR QUE TIPO EXATO DE OBJETO CRIAR E RETORNAR
MUDAR A SUBCLASSE CONCRETA QUE CRIA O OBJETO PERMITE MUDAR A CLASSE DO OBJETO CRIADO SEM QUE CLIENTE SAIBA
PERMITE ESTENDER A FUNCIONALIDADE ATRAVÉS DA CONSTRUÇÃO DE SUBCLASSES SEM AFETAR OS CLIENTES
RESUMINDO:
"CRIE OBJETOS NUMA OPERAÇÃO SEPARADA DE FORMA QUE SUBCLASSES POSSAM FAZER OVERRIDE DA FORMA DE CRIAÇÃO"
QUANDO USAR O PADRÃO FACTORY METHOD?
QUANDO UMA CLASSE (O CRIADOR) NÃO PODE ANTECIPAR A CLASSE DOS OBJETOS QUE DEVE CRIAR
UMA CLASSE QUER QUE SUAS SUBCLASSES ESPECIFIQUEM OS OBJETOS CRIADOS
CLASSES DELEGAM RESPONSABILIDADE PARA UMA ENTRE VÁRIAS SUBCLASSES DE APOIO E QUEREMOS LOCALIZAR NUM PONTO ÚNICO A CONHECIMENTO DE QUAL SUBCLASSE ESTÁ SENDO USADA
ESTRUTURA GENÉRICA
EXERCÍCIO PARA CASA: REDESENHE O DIAGRAMA USANDO INTERFACES
PARTICIPANTES
Produto
DEFINE A INTERFACE DOS OBJETOS CRIADOS PELO FACTORY METHOD
ProdutoConcreto
IMPLEMENTA A INTERFACE Produto
Criador
DECLARA O FACTORY METHOD QUE RETORNA UM OBJETO DO TIPO Produto
ÀS VEZES, O Criador NÃO É UMA CLASSE ABSTRATA E TEM UMA IMPLEMENTAÇÃO DEFAULT PARA O FACTORY METHOD PARA RETORNAR UM OBJETO COM TIPO ProdutoConcreto DEFAULT
PODE CHAMAR O FACTORY METHOD PARA CRIAR UM PRODUTO DO TIPO Produto
CriadorConcreto
FAZ OVERRIDE DO FACTORY METHOD PARA RETORNAR UMA INSTÂNCIA DE ProdutoConcreto
COLABORAÇÕES
Criador DEPENDE DE SUAS SUBCLASSES PARA DEFINIR O FACTORY METHOD PARA QUE ELE RETORNE UMA INSTÂNCIA DO ProdutoConcreto APROPRIADO
CONSEQUÊNCIAS DO USO DO PADRÃO FACTORY METHOD
FACTORY METHODS ELIMINAM A NECESSIDADE DE COLOCAR CLASSES ESPECÍFICAS DA APLICAÇÃO NO CÓDIGO
O CÓDIGO SÓ LIDA COM A INTERFACE Produto
O CÓDIGO PODE PORTANTO FUNCIONAR COM QUALQUER CLASSE ProdutoConcreto
PROVÊ GANCHOS PARA SUBCLASSES
CRIAR OBJETOS DENTRO DE UMA CLASSE COM UM FACTORY METHOD É SEMPRE MAIS FLEXÍVEL DO QUE CRIAR OBJETOS DIRETAMENTE
O FACTORY METHOD PROVÊ UM GANCHO PARA QUE SUBCLASSES FORNEÇAM UMA VERSÃO ESTENDIDA DE UM OBJETO
EXEMPLO NUM EDITOR DE DOCUMENTOS
UMA CLASSE Documento PODERIA TER UM FACTORY METHOD criaFileDialog PARA CRIAR UM OBJETO FILE DIALOG DEFAULT PARA ABRIR UM DOCUMENTO EXISTENTE
UMA SUBCLASSE DE Documento PODERIA CRIAR UM FILE DIALOG ESPECIAL ATRAVÉS DO OVERRIDE DO FACTORY METHOD DEFAULT
NESTE CASO, O FACTORY METHOD NÃO É ABSTRATO MAS FORNECE UM DEFAULT RAZOÁVEL
CONSIDERAÇÕES DE IMPLEMENTAÇÃO
É BOA PRÁTICA USAR UMA CONVENÇÃO DE NOMES PARA ALERTAR PARA O FATO DE QUE ESTÁ USANDO FACTORY METHODS
EXEMPLO: makeABC(), makeXYZ()
EXEMPLO: criaabc(), criaXYZ()
EXEMPLO DE CÓDIGO: CRIAÇÃO DE LABIRINTOS
A CRIAÇÃO DE LABIRINTOS JÁ VISTA NÃO FICOU FLEXÍVEL POIS CRIAMOS (COM new) OS OBJETOS ESPECIFICANDO AS CLASSES CONCRETAS NA FUNÇÃO criaLabirinto
USAREMOS FACTORY METHODS PARA DEIXAR QUE SUBCLASSES ESCOLHAM QUE OBJETOS CRIAR
USAREMOS O SEGUINTE PROJETO
Produto: Sala, Parede, Porta
ProdutoConcreto: Sala, SalaEncantada, SalaPerigosa, Parede, ParedeComBomba, Porta, PortaComChave
Criador: Jogo
SEU MÉTODO criaLabirinto CRIA O LABIRINTO CHAMANDO FACTORY METHODS
ELE TAMBÉM É UM CriadorConcreto POIS OFERECE UMA IMPLEMENTAÇÃO DEFAULT PARA OS FACTORY METHODS (PARA CRIAR UM LABIRINTO SIMPLES)
CriadorConcreto: Jogo, JogoEncantado, JogoPerigoso QUE SERÃO SUBCLASSES DE Jogo
INICIAMOS COM O CRIADOR Jogo QUE CONTÉM OS FACTORY METHODS
classe Jogo { // factory methods com default public Labirinto criaLabirinto() { return new Labirinto(); } public Sala criaSala(int númeroDaSala) { return new Sala(númeroDaSala); } public Parede criaParede() { return new Parede(); } public Porta criaPorta(Sala sala1, Sala sala2) { return new Porta(sala1, sala2); } // Observe que essa função não tem new: ela usa factory methods // Esta é a *única* diferença com relação à versão original public Labirinto criaLabirinto() { Labirinto umLabirinto = criaLabirinto(); Sala sala1 = criaSala(1); Sala sala2 = criaSala(2); Porta aPorta = criaPorta(sala1, sala2); umLabirinto.adicionaSala(sala1); umLabirinto.adicionaSala(sala2); sala1.setVizinho(NORTE, criaParede()); sala1.setVizinho(LESTE, aPorta); sala1.setVizinho(SUL, criaParede()); sala1.setVizinho(OESTE, criaParede()); sala2.setVizinho(NORTE, criaParede()); sala2.setVizinho(LESTE, criaParede()); sala2.setVizinho(SUL, criaParede()); sala2.setVizinho(OESTE, aPorta); return umLabirinto; } }
PARA CRIAR UM JOGO PERIGOSO, CRIAMOS UMA SUBCLASSE DE Jogo E REDEFINIMOS ALGUNS FACTORY METHODS
// Um novo CriadorConcreto class JogoPerigoso extends Jogo { public Parede criaParede() { return new ParedeDestruível(); } public Sala criaSala(int númeroDaSala) { return new SalaComBomba(númeroDaSala); } }
COMPARANDO O DIAGRAMA DE OBJETOS COM A VERSÃO INICIAL, NÃO HÁ OBJETO ADICIONAL
SÓ HÁ UMA MUDANÇA DO OBJETO umJogo PARA UM umJogoPerigoso
PARA CRIAR UM JOGO ENCANTADO, PROCEDEMOS DE FORMA ANÁLOGA
// Um novo CriadorConcreto class JogoEncantado extends Jogo { public Sala criaSala(int númeroDaSala) { return new SalaEncantada(númeroDaSala, jogaEncantamento()); } public Porta criaPorta(Sala sala1, Sala sala2) { return new PortaPrecisandoDeEncantamento(sala1, sala2); } protected Encantamento jogaEncantamento() { ... } }