No outro dia eu estava ouvindo a National Public Radio’s Car Talk, uma popular transmissão semanal durante a qual os chamadores fazem perguntas sobre seus veículos. Antes de cada interrupção de programa, os apresentadores do programa pedem aos ouvintes para discar 1-800-CAR-TALK, que corresponde a 1-800-227-8255. Claro que o primeiro é muito mais fácil de lembrar do que o segundo, em parte porque as palavras “CAR TALK” são um composto: duas palavras que representam sete dígitos. Os seres humanos geralmente acham mais fácil lidar com compósitos do que com seus componentes individuais. Da mesma forma, quando você desenvolve um software orientado a objetos, muitas vezes é conveniente manipular compósitos da mesma forma que você manipula componentes individuais. Essa premissa representa o princípio fundamental do padrão Composite design, o tópico desta parcela Java Design Patterns.

O padrão Composite

Antes de mergulharmos no padrão Composite, devo primeiro definir objetos compostos: objetos que contêm outros objetos; por exemplo, um desenho pode ser composto de primitivos gráficos, tais como linhas, círculos, retângulos, texto, e assim por diante.

Java developers need the Composite pattern because we often must manipulate composites exactly as same way we manipulate primitive objects. Por exemplo, primitivas gráficas como linhas ou texto devem ser desenhadas, movidas e redimensionadas. Mas também queremos realizar a mesma operação em compósitos, tais como desenhos, que são compostos por esses primitivos. Idealmente, gostaríamos de realizar operações tanto em objetos primitivos quanto em compósitos, exatamente da mesma maneira, sem distinguir entre os dois. Se tivermos que distinguir entre objetos primitivos e compósitos para realizar as mesmas operações nesses dois tipos de objetos, nosso código se tornaria mais complexo e mais difícil de implementar, manter e estender.

Em Design Patterns, os autores descrevem o padrão Composite assim:

Compor objetos em estruturas de árvore para representar hierarquias parciais. Composite permite aos clientes tratar objetos individuais e composições de objetos uniformemente.

Implementar o padrão Composite é fácil. As classes compostas estendem uma classe base que representa objetos primitivos. A figura 1 mostra um diagrama de classes que ilustra a estrutura do padrão Composite.

Figure 1. Um diagrama de classes Padrão composto

No diagrama de classes da Figura 1, eu usei nomes de classes da discussão do padrão composto do Design Pattern: Component representa uma classe base (ou possivelmente uma interface) para objetos primitivos, e Composite representa uma classe composta. Por exemplo, a classe Component pode representar uma classe base para primitivas gráficas, enquanto que a classe Composite pode representar uma classe Drawing. A figura 1 é Leaf class representa um objeto primitivo concreto; por exemplo, uma Line class ou uma Text class. Os métodos Operation1() e Operation2() representam métodos específicos de domínio implementados por ambos os métodos Component e Composite classes.

A classe Composite mantém uma coleção de componentes. Tipicamente, Composite métodos são implementados iterando sobre essa coleção e invocando o método apropriado para cada Component na coleção. Por exemplo, uma classe Drawing pode implementar seu método draw() assim:

Para cada método implementado na classe Component, a classe Composite implementa um método com a mesma assinatura que itera sobre os componentes da composição, como ilustrado pelo método draw() listado acima.

A classe Composite estende a classe Component, para que você possa passar um composto para um método que espera um componente; por exemplo, considere o seguinte método:

// This method is implemented in a class that's unrelated to the// Component and Composite classespublic void repaint(Component component) { // The component can be a composite, but since it extends // the Component class, this method need not // distinguish between components and composites component.draw();}

O método anterior é passado a um componente – seja um componente simples ou um composto – então ele invoca o método draw() desse componente. Porque a classe Composite estende Component, o método repaint() não precisa distinguir entre componentes e compósitos – ele simplesmente invoca o método draw() para o componente (ou compósito).

O diagrama de classes de padrão composto de 1 ilustra um problema com o padrão: você deve distinguir entre componentes e compósitos quando faz referência a um Component, e você deve invocar um método específico composto, tal como addComponent(). Normalmente, o usuário preenche esse requisito adicionando um método, como isComposite(), à classe Component. Esse método retorna false para componentes e é sobreposto na classe Composite para retornar true. Adicionalmente, você também deve lançar a referência Component para uma instância Composite, assim:

Note que o método addComponent() é passado uma referência Component, que pode ser ou um componente primitivo ou um composto. Como esse componente pode ser um composto, você pode compor componentes em uma estrutura em árvore, como indicado pela citação acima de Design Patterns.

Figure 2 mostra uma implementação alternativa de padrões Composite.

Figure 2. Um diagrama de classes alternativo do Composite pattern

Se você implementar o Composite pattern da Figura 2, você nunca terá que distinguir entre componentes e compósitos, e não terá que lançar uma Component referência a uma instância de Composite. Então o fragmento de código listado acima reduz a uma única linha:

...component.addComponent(someComponentThatCouldBeAComposite);...

mas, se o Component referência no fragmento de código anterior não se refere a um Composite, o que deve fazer o addComponent()? Esse é um grande ponto de discórdia com a implementação do padrão composto da Figura 2. Como os componentes primitivos não contêm outros componentes, adicionar um componente a outro não faz sentido, então o método Component.addComponent() pode falhar silenciosamente ou lançar uma exceção. Tipicamente, adicionar um componente a outro componente primitivo é considerado um erro, então lançar uma exceção é talvez o melhor curso de ação.

Então qual implementação de padrão composto – o da Figura 1 ou o da Figura 2 – funciona melhor? Esse é sempre um tópico de grande debate entre os implementadores de padrões Compostos; Design Patterns prefere a implementação da Figura 2 porque você nunca precisa distinguir entre componentes e recipientes, e nunca precisa executar um molde. Pessoalmente, eu prefiro a implementação da Figura 1, porque eu tenho uma forte aversão a métodos de implementação em uma classe que não fazem sentido para aquele tipo de objeto.

Agora que você entenda o padrão Composite e como você pode implementá-lo, vamos examinar um exemplo de padrão Composite com o framework Apache Struts JavaServer Pages (JSP).

O padrão Composite e Struts Tiles

O framework Apache Struts inclui uma biblioteca de tags JSP, conhecida como Tiles, que lhe permite compor uma página Web a partir de múltiplos JSPs. O Tiles é na verdade uma implementação do padrão J2EE (Java 2 Platform, Enterprise Edition) CompositeView, ele próprio baseado no padrão Design Patterns Composite. Antes de discutirmos a relevância do padrão Composite para a biblioteca de tags do Tiles, vamos primeiro rever a lógica do Tiles, e como você o utiliza. Se você já está familiarizado com Struts Tiles, você pode folhear as seções seguintes e começar a ler em “Use the Composite Pattern with Struts Tiles”

Note: Você pode ler mais sobre o padrão J2EE CompositeView no meu artigo “Web Application Components Made Easy with Composite View” (JavaWorld, dezembro de 2001).

Designers frequentemente constroem páginas Web com um conjunto de regiões discretas; por exemplo, a página Web da Figura 3 compreende uma barra lateral, cabeçalho, região de conteúdo e rodapé.

Figure 3. O Padrão Composto e Struts Tiles. Clique na miniatura para ver a imagem em tamanho real.

Websites geralmente incluem várias páginas Web com layouts idênticos, como a barra lateral/cabeçalho/conteúdo/rodapé da Figura 3. Struts Tiles permite a reutilização tanto do conteúdo quanto do layout entre várias páginas da Web. Antes de discutirmos essa reutilização, vamos ver como o layout da Figura 3 é tradicionalmente implementado apenas com HTML.

Implementar layouts complexos à mão

Exemplo 1 mostra como você pode implementar a página da Figura 3 com HTML:

Exemplo 1. Um layout complexo implementado à mão

O JSP anterior tem dois grandes inconvenientes: Primeiro, o conteúdo da página está embutido no JSP, então você não pode reutilizar nada dele, mesmo que a barra lateral, o cabeçalho e o rodapé sejam provavelmente os mesmos em muitas páginas da Web. Segundo, o layout da página também está embutido no JSP, então você também não pode reutilizá-la, mesmo que muitas outras páginas do mesmo site usem o mesmo layout. Podemos usar a ação <jsp:include> para remediar o primeiro inconveniente, como discuto a seguir.

Implementar layouts complexos com JSP inclui

Exemplo 2 mostra uma implementação da Página Web da Figura 3 que usa <jsp:include>:

Exemplo 2. Um layout complexo implementado com JSP inclui

O JSP anterior inclui o conteúdo de outros JSPs com <jsp:include>. Como eu encapsulei esse conteúdo em JSPs separados, você pode reutilizá-lo para outras páginas Web:

Exemplo 3. sidebar.jsp

Para completar, eu listei abaixo os JSPs incluídos pelo JSP anterior:

Exemplo 4. header.jsp

<font size='6'>Welcome to Sabreware, Inc.</font><hr>

Exemplo 5. content.jsp

<font size='4'>Page-specific content goes here</font>

Exemplo 6. footer.jsp

<hr>Thanks for stopping by!

Even embora o JSP do Exemplo 2 use <jsp:include> para reutilizar o conteúdo, você não pode reutilizar o layout da página porque ele está codificado no JSP. Struts Tiles permite reutilizar tanto o conteúdo quanto o layout, como ilustrado na próxima seção.

Implementar layouts complexos com Struts Tiles

Exemplo 7 mostra a Página Web na Figura 3 implementada com Struts Tiles:

Exemplo 7. Use Struts Tiles para encapsular o layout

O JSP anterior usa a tag <tiles:insert> para criar o JSP da Figura 3. Esse JSP é definido por uma definição de tiles chamada sidebar-header-footer-definition. Essa definição reside no arquivo de configuração Tiles, que neste caso é WEB-INF/tiles-defs.xml, listado no Exemplo 8:

Exemplo 8. WEB-INF/tiles-defs.xml

A definição anterior do Tiles especifica o layout da página, encapsulado em header-footer-sidebar-layout.jsp, e o conteúdo da página, encapsulado em sidebar.jsp, header.jsp, content.jsp, e footer.jsp, conforme listados nos Exemplos 3-6. O Exemplo 9 lista o JSP que define o layout –header-footer-sidebar-layout.jsp:

Exemplo 9. header-footer-sidebar-layout.jsp

O JSP anterior encapsula o layout e insere o conteúdo de acordo com os valores especificados para a barra lateral, editor, conteúdo e regiões de rodapé no arquivo de definição de tiles, facilitando assim a reutilização tanto para o conteúdo quanto para o layout. Por exemplo, você poderia definir outra definição de Tiles com o mesmo layout, a mesma barra lateral, editor e rodapé, mas conteúdo diferente:

Para criar o JSP definido pela definição de tiles a-different-sidebar-header-footer-definition, você usa a tag <tiles:insert>, como esta:

Página Struts Tiles, você pode reutilizar tanto o conteúdo quanto o layout, o que se mostra inestimável para Websites com muitos JSPs que compartilham layout e algum conteúdo. Mas se você olhar atentamente para o código dos Exemplos 7-9, você notará que o layout para a região da barra lateral é hardcoded em sidebar.jsp, que está listado no Exemplo 3. Isso significa que você não pode reutilizar esse layout. Felizmente, a biblioteca de tags Tiles implementa o padrão Composite, que nos permite especificar uma definição de tiles – em vez de um JSP – para uma região. Na próxima seção, eu explico como usar essa implementação de padrões Composite.

Utilizar o padrão Composite com Struts Tiles

Struts Tiles implementa o padrão Composite, onde a classe Component é representada por JSPs e a classe Composite é representada por uma definição de Tiles. Essa implementação permite especificar um JSP (um componente) ou uma definição de Pisos (um composto) como o conteúdo para a região de um JSP. O exemplo 10 ilustra essa característica:

Exemplo 10. WEB-INF/tiles-defs.xml: Use o padrão Composite

O arquivo de configuração de Tiles anterior define duas definições de Tiles: sidebar-definition e sidebar-header-footer-definition. O sidebar-definition é especificado como o valor para a região da barra lateral no arquivo sidebar-header-footer-definition. Você pode especificá-lo como tal porque o Tiles implementa o padrão Composto deixando o Tiles especificar uma definição (um Composite que é uma coleção JSP) onde você normalmente especificaria um único JSP (que é um Component).

O layout da barra lateral é encapsulado no Exemplo 11’s sidebar-layout.jsp:

Exemplo 11. sidebar-layout.jsp

Exemplo 12 listas flags.jsp, especificado como o conteúdo da barra lateral top região, e o Exemplo 13 listas sidebar-links.jsp, especificado como a barra lateral bottom região:

Exemplo 12. flags.jsp

Exemplo 13. sidebar-links.jsp

Agora o sidebar-definition pode definir outras regiões com um componente superior e inferior, embora você provavelmente deveria renomear essa definição para algo mais genérico como top-bottom-definition.

É tudo composto hoje em dia

O padrão composto é popular com estruturas de apresentação, como Swing e Struts, porque ele permite que você aninhe os recipientes tratando os componentes e seus recipientes exatamente da mesma forma. Struts Tiles usa o padrão Composite para especificar um simples JSP ou uma definição de Tiles – que é uma coleção de JSP – como conteúdo de um ladrilho. Essa é uma capacidade poderosa que facilita o gerenciamento de grandes Websites com diferentes layouts.

A seção “Homework from Last Time” abaixo expande a discussão deste artigo internacionalizando a aplicação anterior com uma ação Struts e a JSP Standard Tag Library (JSTL).

Homework

Discutam como Swing implementa o padrão composto com as classes Component e Container.

Trabalho de casa da última vez

Sua última atribuição pediu para você baixar Struts de http://jakarta.apache.org/struts/index.html e implementar sua própria classe de ação Struts.

Para esta atribuição, eu decidi implementar uma ação Struts em conjunto com a JSP Standard Tag Library (JSTL) para internacionalizar a aplicação Web do Exemplo 1. Embora Struts forneça a infra-estrutura necessária para internacionalizar suas aplicações Web, você deve usar JSTL para essa tarefa, porque JSTL é um padrão. Em algum momento, as capacidades de internacionalização Struts serão provavelmente depreciadas ou integradas com JSTL.

Após internacionalizar a aplicação Web do Exemplo 1 com uma ação Struts e JSTL, eu localizei essa aplicação para o chinês. A Figura H1 ilustra o resultado.

Note: Eu não sei uma única palavra de chinês, muito menos como escrever o idioma, então a Figura H1 é fabricada a partir de strings Unicode arbitrárias. Mas parece legal, independentemente.

Figure H1. Internacionalização com uma Ação de Struts. Clique na miniatura para ver a imagem em tamanho real.

Notice I specified the href attributes in Example 12’s flags.jsp as empty strings, so when you click on the flags, the servlet container reloads the current JSP. Portanto, o primeiro passo para internacionalizar a aplicação Web é especificar uma URL para esses atributos, como listado no Exemplo H1:

Exemplo H1. flags.jsp

A URL para cada atributo href é a mesma: flags.do. Dois parâmetros de requisição são tacked naquela URL: um para o locale correspondente ao flag e outro que representa o caminho atual do JSP. Você obtém o último parâmetro de requisição invocando o método Http.ServletRequest.getServletPath().

No descritor de implantação da aplicação, eu mapeei todas as URLs terminando em .do para o servlet de ação Struts, assim:

Exemplo H2. WEB-INF/web.xml (Trecho)

Nota: Veja meu artigo “Take Command of Your Software” (JavaWorld, junho de 2002) para mais informações sobre Struts e o servlet de ação Struts.

Próximo, mapeei o caminho /flags para a ação Struts actions.FlagAction no arquivo de configuração Struts, listado no Exemplo H3:

Exemplo H3. WEB-INF/struts-config.xml

Por causa desse mapeamento, a URL flags.do faz com que o servlet da ação Struts invoque o método actions.FlagAction.execute(); portanto, clicando em uma flag irá invocar o método actions.FlagAction.execute(). O exemplo H4 lista o actions.FlagAction class:

Exemplo H4. WEB-INF/classes/actions/FlagAction.java

O método actions.FlagAction.execute() obtém uma referência ao parâmetro locale request e passa esse valor para o método JSTL Config class’s set(). Esse método armazena a string representando o locale na configuração FMT_LOCALE, que JSTL usa para localizar números de texto e formato, moedas, porcentagens e datas.

Agora eu especifiquei um locale para ações de internacionalização JSTL, eu em seguida especifiquei um pacote de recursos no descritor de implantação da aplicação, um trecho do qual é listado no Exemplo H5:

Exemplo H5. WEB-INF/web.xml (Trecho)

O nome base do pacote de recursos resources é especificado para o parâmetro javax.servlet.jsp.jstl.fmt.localizationContext inicialização de contexto. Isso significa que JSTL irá procurar por um arquivo chamado resources_en.properties quando o locale estiver definido para inglês britânico (en-GB) e resources_zh.properties quando o locale estiver definido para chinês (zh-ZH).

Agora eu especifiquei um pacote de recursos e um locale para as ações de internacionalização de JSTL, eu modifico em seguida os arquivos JSP para usar essas ações, como exemplos H6-H9 demonstram:

Exemplo H6. sidebar-links.jsp

Exemplo H7. header.jsp

Exemplo H8. content.jsp

Exemplo H9. footer.jsp

Exemplos H6-H9’s JSPs usam o JSTL <fmt:message> ação para extrair strings Unicode do pacote de recursos especificado no descritor de deployment. Exemplos H10 e H11 listam os pacotes de recursos para inglês e chinês, respectivamente:

Exemplo H10. WEB-INF/classes/resources_en.properties

Exemplo H11. WEB-INF/classes/resources_zh.properties

Email

Em um e-mail para mim, Joseph Friedman escreveu:

Em “Decorate Your Java Code” (JavaWorld, Dezembro 2001), você escreve:

“O objecto envolvente – conhecido como decorador – está em conformidade com a interface do objecto que envolve, permitindo que o decorador seja utilizado como se fosse uma instância do objecto que envolve.”

No entanto, o exemplo de código decora o FileReader com um LineNumberReader e chama readLine(), que não está na interface FileReader; assim você não está usando a interface LineNumberReader transparentemente.

Bambos FileReader e LineNumberReader estão em conformidade com a interface definida pela classe Reader, que ambos estendem. A idéia é que você pode passar o decorador para qualquer método que espere uma referência a um Reader. Esses métodos continuam a ignorar as capacidades especiais desse decorador – neste caso, a capacidade de ler ficheiros e produzir números de linha. No entanto, se souber dessas capacidades especiais, pode tirar partido delas.

O facto de o decorador possuir (um ou mais) métodos que falta ao objecto decorado não viola de forma alguma a intenção do padrão Decorator; na verdade, essa é a característica central desse padrão: adicionar funcionalidade em tempo de execução a um objecto decorando objectos recursivamente.

Se olhar para a página 173 de Padrões de Design, verá isto:

As subclasses do Decorador são livres para adicionar operações para funcionalidades específicas. Por exemplo, ScrollDecorator‘s ScrollTo operação permite que outros objetos rolem a interface *se* eles souberem que existe um objeto ScrollDecorator na interface.

DavidGeary é o autor do Core JSTL Mastering the JSP Standard TagLibrary, que será publicado neste outono porPrentice-Hall e Sun Microsystems Press; Advanced JavaServer Pages (PrenticeHall, 2001; ISBN: 0130307041); e a série Graphic Java (Sun MicrosystemsPress). David vem desenvolvendo software orientado a objetos com inúmeras linguagens orientadas a objetos há 18 anos. Desde que o livro GOFDesign Patterns foi publicado em 1994, David tem sido um defensor ativo dos padrões de design, e tem usado e implementado padrões de design em Smalltalk, C++, e Java. Em 1997, David começou a trabalhar em tempo integral como autor e ocasionalmente como palestrante e consultor. David é membro dos grupos de especialistas que definem a biblioteca de tags personalizadas padrão JSP e JavaServer Faces, e é colaborador do Apache Struts JSP framework.

Saiba mais sobre este tópico

Deixe uma resposta

O seu endereço de email não será publicado.