SEMELHANTE A WORD, POR EXEMPLO
O EDITOR PODE MISTURAR TEXTO E GRÁFICOS USANDO VÁRIAS OPÇÕES DE FORMATAÇÃO
A REDOR DA ÁREA DE EDIÇÃO ESTÃO OS MENUS, SCROLL BARS, BARRAS DE FERRAMENTAS, ETC.
O PRIMEIRO PROBLEMA DE DESIGN QUE QUEREMOS ATACAR É COMO REPRESENTAR A ESTRUTURA DO DOCUMENTO
ESTA ESTRUTURA AFETA O RESTO DA APLICAÇÃO JÁ QUE A EDIÇÃO, FORMATAÇÃO, ANÁLISE TEXTUAL, ETC. DEVERÃO ACESSAR A REPRESENTAÇÃO DO DOCUMENTO
UM DOCUMENTO É UM ARRANJO DE ELEMENTOS GRÁFICOS BÁSICOS
CARACTERES, LINHAS, POLÍGONOS E OUTRAS FIGURAS
O USUÁRIO NORMALMENTE NÃO PENSA EM TERMOS DESSES ELEMENTOS GRÁFICOS MAS EM TERMOS DE ESTRUTURAS FÍSICAS
LINHAS, COLUNAS, FIGURAS, TABELAS E OUTRAS SUB-ESTRUTURAS
AS SUB-ESTRUTURAS PODEM SE COMPOSTAS DE OUTRAS SUB-ESTRUTURAS, ETC.
O USUÁRIO TAMBÉM PODE PENSAR NA ESTRUTURA LÓGICA (FRASE, PARÁGRAFOS, SEÇÕES, ETC.)
NÃO VAMOS CONSIDERAR ISSO AQUI MAS A SOLUÇÃO QUE USAREMOS SE APLICA A ESTA SITUAÇÃO TAMBÉM
A INTERFACE DO USUÁRIO (UI) DO EDITOR DEVE PERMITIR QUE O USUÁRIO MANIPULE TAIS SUB-ESTRUTURAS DIRETAMENTE
POR EXEMPLO, O USUÁRIO PODE TRATAR UM DIAGRAMA COMO UMA UNIDADE EM VEZ DE UMA COLEÇÃO DE ELEMENTOS GRÁFICOS PRIMITIVOS
O USUÁRIO DEVE MANIPULAR UMA TABELA COMO UMA UNIDADE E NÃO COMO UM AMONTOADO DE TEXTO E GRÁFICOS
PARA PERMITIR TAL MANIPULAÇÃO, USAREMOS UMA REPRESENTAÇÃO INTERNA QUE CASE COM A ESTRUTURA FÍSICA DO DOCUMENTO
PORTANTO, A REPRESENTAÇÃO INTERNA DEVE SUPORTAR:
A MANUTENÇÃO DA ESTRUTURA FÍSICA DO DOCUMENTO (O ARRANJO DE TEXTO E GRÁFICOS EM LINHAS, COLUNAS, TABELAS, ...)
GERAÇÃO E APRESENTAÇÃO VISUAL DO DOCUMENTO
MAPEAR POSIÇÕES DA TELA PARA ELEMENTOS DA REPRESENTAÇÃO INTERNA. O EDITOR VAI SABER PARA QUE O USUÁRIO ESTÁ APONTANDO
TEM ALGUMAS OUTRAS RESTRIÇÕES NO PROJETO
TEXTO E GRÁFICOS DEVEM SER TRATADOS UNIFORMEMENTE
A INTERFACE DEVE PERMITIR EMBUTIR TEXTO EM GRÁFICOS E VICE VERSA
GRÁFICOS NÃO DEVEM SER TRATADOS COMO CASOS ESPECIAIS DE TEXTO, NEM TEXTO COMO CASO ESPECIAL DE GRÁFICO, SENÃO TEREMOS MECANISMOS REDUNDANTES DE MANIPULAÇÃO, FORMATAÇÃO, ETC.
A IMPLEMENTAÇÃO NÃO DEVERIA TER QUE DIFERENCIAR ENTRE ELEMENTOS ÚNICOS E GRUPOS DE ELEMENTOS NA REPRESENTAÇÃO INTERNA
O EDITOR DEVE TRATAR ELEMENTOS SIMPLES E COMPLEXOS DE FORMA UNIFORME PERMITINDO ASSIM DOCUMENTOS ARBITRARIAMENTE COMPLEXOS
O DÉCIMO ELEMENTO DA LINHA 5, COLUNA 2 PODE SER UM CARACTERE ÚNICO OU UM DIAGRAMA COMPLEXO COM MUITOS ELEMENTOS
BASTA QUE O ELEMENTO SAIBA SE DESENHAR, POSSA DAR SUAS DIMENSÕES: ELE PODE TER QUALQUER COMPLEXIDADE
POR OUTRO LADO, QUEREMOS ANALISAR O TEXTO PARA VERIFICAR A GRAFIA, HIFENIZAR, ETC. E NÃO PODEMOS VERIFICAR A GRAFIA DE GRÁFICOS OU HIFENIZÁ-LAS
COMPOSIÇÃO RECURSIVA
UMA FORMA COMUM DE REPRESENTAR INFORMAÇÃO ESTRUTURADA HIERARQUICAMENTE É ATRAVÉS DA COMPOSIÇÃO RECURSIVA
PERMITE CONSTRUIR ELEMENTOS COMPLEXOS A PARTIR DE ELEMENTOS SIMPLES
AQUI, A COMPOSIÇÃO RECURSIVA VAI PERMITIR COMPOR UM DOCUMENTO A PARTIR DE ELEMENTOS GRÁFICOS SIMPLES
COMEÇAMOS FORMANDO LINHAS A PARTIR DE ELEMENTOS GRÁFICOS SIMPLES (CARACTERES E GRÁFICOS)
MÚLTIPLAS LINHAS FORMAM COLUNAS
MÚLTIPLAS COLUNAS FORMAM PÁGINAS
MÚLTIPLAS PÁGINAS FORMAM DOCUMENTOS
ETC.
PODEMOS REPRESENTAR ESSA ESTRUTURA FÍSICA USANDO UM OBJETO PARA CADA ELEMENTO
ISSO INCLUI ELEMENTOS VISÍVEIS E ELEMENTOS ESTRUTURAIS (LINHAS, COLUNAS)
A ESTRUTURA DE OBJETOS SERIA COMO ABAIXO
NA PRÁTICA, TALVEZ UM OBJETO NÃO FOSSE USADO PARA CADA CARACTERE POR RAZÕES DE EFICIÊNCIA
PODEMOS AGORA TRATAR TEXTO E GRÁFICOS DE FORMA UNIFORME
PODEMOS AINDA TRATAR ELEMENTOS SIMPLES E COMPOSTOS DE FORMA UNIFORME
TEREMOS QUE TER UMA CLASSE PARA CADA TIPO DE OBJETO E ESSAS CLASSES TERÃO QUE TER A MESMA INTERFACE (PARA TER UNIFORMIDADE DE TRATAMENTO)
UMA FORMA DE TER INTERFACES COMPATÍVEIS É DE USAR HERANÇA
DEFINIMOS UMA CLASSE ABSTRATA "ElementoDeDocumento" PARA TODOS OS ELEMENTOS QUE APARECEM NA ESTRUTURA DE OBJETOS
SUAS SUBCLASSES DEFINEM ELEMENTOS GRÁFICOS PRIMITIVOS (CARACTERES E GRÁFICOS) E ELEMENTOS ESTRUTURAIS (LINHAS, COLUNAS, FRAMES, PÁGINAS, DOCUMENTOS, ...)
PARTE DA HIERARQUIA DE CLASSES SEGUE ABAIXO
ELEMENTOS DE DOCUMENTOS TÊM TRÊS RESPONSABILIDADES
SABEM SE DESENHAR (draw)
SABEM O ESPAÇO QUE OCUPAM (limites)
SABEM SEUS FILHOS E PAI
SUBCLASSES DE ElementoDeDocumento DEVEM REDEFINIR CERTAS OPERAÇÕES TAIS COMO draw
UM ElementoDeDocumento PAI DEVE FREQUENTEMENTE SABER QUANTO ESPAÇO SEUS FILHOS OCUPAM PARA POSICIONÁ-LOS DE FORMA A NÃO HAVER SOBREPOSIÇÃO
ISSO É FEITO COM O MÉTODO limites QUE RETORNA O RETÂNGULO QUE CONTÉM O ELEMENTO
A OPERAÇÃO intersecta SERVE PARA SABER SE UM PONTO INTERSECTA O ELEMENTO
USADO QUANDO O USUÁRIO CLICA COM O MOUSE
A CLASSE ABSTRATA ElementoCompostoDeDocumento IMPLEMENTA CERTOS MÉTODOS COMUNS ATRAVÉS DA APLICAÇÃO SUCESSIVA DO MÉTODO AOS FILHOS
É O CASO DE intersecta
TODOS OS ELEMENTOS TÊM UMA MESMA INTERFACE PARA GERENCIAR OS FILHOS (INSERIR, REMOVER, ETC.)
ESTE PADRÃO DE PROJETO CHAMA-SE COMPOSITE E SERÁ DISCUTIDO MAIS DETALHADAMENTE AGORA
COMPOSITE
OBJETIVO
COMPOR OBJETOS EM ESTRUTURAS EM ÁRVORE PARA REPRESENTAR HIERÁRQUIAS PARTE-TODO
COMPOSITE PERMITE QUE CLIENTES TRATEM OBJETOS INDIVIDUAIS E COMPOSIÇÕES UNIFORMEMENTE
PARTICIPANTES
OS NOMES GENÉRICOS DADOS ÀS CLASSES ABSTRATAS SÃO COMPONENT E COMPOSITE
OS NOMES GENÉRICOS DADOS ÀS CLASSES CONCRETAS SÃO LEAF E CONCRETE COMPOSITE
EXERCÍCIO PARA CASA: REDESENHAR O DIAGRAMA ACIMA UTILIZANDO INTERFACES
CONSEQUÊNCIAS DO USO DE COMPOSITE
OBJETOS COMPLEXOS PODEM SER COMPOSTOS DE OBJETOS MAIS SIMPLES RECURSIVAMENTE
O CLIENTE PODE TRATAR OBJETOS SIMPLES OU COMPOSTOS DA MESMA FORMA: SIMPLIFICA O CLIENTE
FACILITA A ADIÇÃO DE NOVOS COMPONENTES: O CLIENTE NÃO TEM QUE MUDAR COM A ADIÇÃO DE NOVOS OBJETOS (SIMPLES OU COMPOSTOS)
DO LADO NEGATIVO, O PROJETO FICA GERAL DEMAIS
É MAIS DIFÍCIL RESTRINGIR OS COMPONENTES DE UM OBJETO COMPOSTO
POR EXEMPLO, NO EXEMPLO ACIMA, PODEMOS COMPOR LINHAS COM LINHAS OU COM DOCUMENTOS, ETC. O QUE NÃO FAZ SENTIDO
O SISTEMA DE TIPAGEM DA LINGUAGEM NÃO AJUDA A DETECTAR COMPOSIÇÕES ERRADAS
A SOLUÇÃO É VERIFICAR EM TEMPO DE EXECUÇÃO
CONSIDERAÇÕES DE IMPLEMENTAÇÃO
ADICIONAR REFERÊNCIA AO PAI DE UM OBJETO PODE SIMPLIFICAR O CAMINHAMENTO NA ESTRUTURA
ONDE ADICIONAR A REFERÊNCIA AO PAI? NORMALMENTE É COLOCADO NA CLASSE ABSTRATA COMPONENT
AS SUBCLASSES HERDAM A REFERÊNCIA E OS MÉTODOS QUE A GERENCIAM
COMPARTILHAMENTO DE COMPONENTES
ÚTIL PARA REDUZIR AS NECESSIDADES DE ESPAÇO
POR EXEMPLO, CARACTERES IGUAIS PODERIAM COMPARTILHAR OBJETOS
FAZER ISSO COMPLICA SE OS COMPONENTES SÓ PUDEREM TER UM PAI
O PADRÃO "FLYWEIGHT" MOSTRA COMO RESOLVER A QUESTÃO
MAXIMIZAÇÃO DA INTERFACE DE COMPONENT
EXERCÍCIO PARA CASA: CERTOS LIVROS COLOCAM OS MÉTODOS DE GERENCIAMENTO DE FILHOS APENAS NA CLASSE COMPOSITE PORQUE UMA FOLHA NÃO TEM FILHOS!
VOCÊ CONCORDA OU DISCORDA COM A MAXIMIZAÇÃO DA INTERFACE DE COMPONENT? POR QUÊ? EM OUTRAS PALAVRAS, É MELHOR TER UMA INTERFACE IDÊNTICA PARA FOLHAS E COMPOSITE (TRANSPARÊNCIA PARA O CLIENTE) OU INTERFACES DIFERENTES (SEGURANÇA DE NÃO FAZER BESTEIRAS COMO ADICIONAR UM FILHO A UMA FOLHA, O QUE SERIA CAPTURADO PELO COMPILADOR)?
SE MANTIVERMOS AS INTERFACES DE COMPONENT E COMPOSITE DIFERENTES, COMO O CLIENTE PODE TESTAR SE UM OBJETO É FOLHA OU COMPOSTO?
ONDE SÃO ARMAZENADOS OS FILHOS?
NÓS OS COLOCAMOS EM COMPOSITE MAS ELES PODERIAM SER COLOCADOS EM COMPONENT
A DESVANTAGEM É A PERDA DE ESPAÇO PARA ESSA REFERÊNCIA PARA FOLHAS
QUANDO OS FILHOS DEVEM TER UM ORDEM ESPECIAL, DEVE-SE CUIDAR DESTE ASPECTO
USAR UM ITERATOR (ENUMERATION) É UMA BOA IDÉIA
CACHE DE INFORMAÇÃO
AS CLASSES COMPOSITE PODEM MANTER EM CACHE INFORMAÇÃO SOBRE SEUS FILHOS DE FORMA A ELIMINAR (CURTO-CIRCUITAR) O CAMINHAMENTO OU PESQUISA NOS FILHOS
UM EXEMPLO: UM COMPOSITE PODERIA MANTER EM CACHE OS LIMITES DO CONJUNTO DE FILHOS DE FORMA A NÃO TER QUE RECALCULAR ISSO SEMPRE
QUANDO UM FILHO MUDA, A CACHE DEVE SER INVALIDADA
NESTE CASO, OS FILHOS DEVEM CONHECER O PAI PARA AVISAR DA MUDANÇA
COMO ARMAZENAR OS FILHOS?
VECTOR, LISTA ENCADEADA, HASHTABLE
USO DO PADRÃO NA API JAVA: O PACOTE java.awt.swing
USA O PADRÃO COMPOSITE COM A CLASSE ABSTRATA "Component" E O COMPOSITE SENDO A CLASSE "Container"
FOLHAS PODEM SER Label, TextField, Button
COMPOSITES CONCRETOS SÃO Panel, Frame, Dialog
EXEMPLO DE CÓDIGO: UM EDITOR DE DOCUMENTOS
O DIAGRAMA DE CLASSES SEGUE
abstract class ElementoDeDocumento // Este é o font associado ao objeto // Se for nulo, o font é herdado do pai private Font font; ElementoCompostoDeDocumento pai; // o container deste objeto ... public ElementoCompostoDeDocumento getPai() { return pai; } /** * Retorna o font associado a este objeto. * Se não houver font, retorna o font do pai. * Se não houver pai, retorna null. */ public Font getFont() { if( font != null ) { return font; } else if( pai != null ) { return pai.getFont(); } else { return null; } } // getFont() /** * Associa um font a este objeto. */ public void setFont( Font font) { this.font = font; } // setFont() /** * Retorna o número de caracteres que este objeto contém. */ public abstract int getNumCarac(); /** * Retona o filho deste objeto na posição dada. */ public ElementoDeDocumento getFilho( int índice ) throws ÉFolhaException { throw new ÉFolhaException( "Folha não tem filhos" ); } /** * Faça o ElementoDeDocumento dado um filho deste objeto. */ public abstract void insereFilho( ElementoDeDocumento filho ); public void insereFilho( ElementoDeDocumento filho ) throws ÉFolhaException { throw new ÉFolhaException( "Folha não tem filhos" ); } /** * Remove o ElementoDeDocumento dado da lista de filhos deste objeto. */ public void removeFilho( ElementoDeDocumento filho ); throws ÉFolhaException { throw new ÉFolhaException( "Folha não tem filhos" ); } } // class ElementoDeDocumento abstract class ElementoCompostoDeDocumento extends ElementoDeDocumento { // Os filhos deste objeto private Vector filhos = new Vector(); // valor em cache de getNumCarac private int cacheNumCarac = -1; // -1 significa sem valor em cache /** * Retona o filho deste objeto na posição dada. */ public ElementoDeDocumento getFilho( int índice ) { return (ElementoDeDocumento)filhos.elementAt( índice ); } // getFilho() /** * Faça o ElementoDeDocumento dado um filho deste objeto. */ public synchronized void insereFilho( ElementoDeDocumento filho ) { synchronized (filho) { filhos.addElement( filho ); filho.pai = this; avisoDeMudança(); } // synchronized } // insereFilho() /** * Remove o ElementoDeDocumento dado da lista de filhos deste objeto. */ public synchronized void removeFilho( ElementoDeDocumento filho ) { synchronized (filho) { if( this == filho.pai ) { filho.pai = null; } filhos.removeElement( filho ); avisoDeMudança(); } // synchronized } // removeFilho() ... /** * Uma chamada a esta função significa que um filho mudou * o que invalida a cache de informação que o objeto mantém * sobre seus filhos. */ public void avisoDeMudança() { cacheNumCarac = -1; if( pai != null ) { pai.avisoDeMudança(); } } // avisoDeMudança() /** * retorna o número de caracteres que este objeto contém. */ public int getNumCarac() { if(cacheNumCarac >= 0) { return cacheNumCarac; } cacheNumCarac = 0; for( int i = 0; i < filhos.size(); i++ ) { ElementoDeDocumento filho; filho = (ElementoDeDocumento)filhos.elementAt( i ); cacheNumCarac += filho.getNumCarac(); } // for return cacheNumCarac; } // getNumCarac() } // class ElementoCompostoDeDocumento class Caractere extends ElementoDeDocumento { ... /** * retorna o número de caracteres que este objeto contém. */ public int getNumCarac() { return 1; } // getNumCarac() } // class Caractere class Imagem extends ElementoDeDocumento { ... /** * retorna o número de caracteres que este objeto contém. */ public int getNumCarac() { return 1; // uma imagem é considerada um "grande" caractere } // getNumCarac() } // class Imagem class Página extends ElementoCompostoDeDocumento { ... } // Página
ALGUNS COMENTÁRIOS
getFont USA A INFORMAÇÃO DE SEU PAI
UM OBJETO COMPOSTO USA UMA CACHE PARA SABER QUANTOS CARACTERES COMPÕEM O OBJETO
synchronized É USADO AO MEXER COM DADOS QUE PODEM SER COMPARTILHADOS PARA POSSIBILITAR UMA IMPLEMENTAÇÃO MULTI-THREADED