El otro día estaba escuchando el programa Car Talk de National Public Radio, una popular emisión semanal en la que los oyentes hacen preguntas sobre sus vehículos. Antes de cada pausa del programa, los presentadores piden a los que llaman que marquen el 1-800-CAR-TALK, que corresponde al 1-800-227-8255. Por supuesto, el primero resulta mucho más fácil de recordar que el segundo, en parte porque las palabras «CAR TALK» son un compuesto: dos palabras que representan siete dígitos. A los humanos les resulta más fácil tratar con compuestos que con sus componentes individuales. Del mismo modo, cuando se desarrolla un software orientado a objetos, suele ser conveniente manipular los compuestos igual que se manipulan los componentes individuales. Esa premisa representa el principio fundamental del patrón de diseño Composite, el tema de esta entrega de Java Design Patterns.
- El patrón Composite
- El patrón Composite y Struts Tiles
- Implementa diseños complejos a mano
- Ejemplo 1. Un diseño complejo implementado a mano
- Implementar diseños complejos con JSP includes
- Ejemplo 2. Un diseño complejo implementado con JSP incluye
- Ejemplo 3. sidebar.jsp
- Ejemplo 4. header.jsp
- Ejemplo 5. content.jsp
- Ejemplo 6. footer.jsp
- Implementar diseños complejos con Struts Tiles
- Ejemplo 7. Utilizar Struts Tiles para encapsular el layout
- Ejemplo 8. WEB-INF/tiles-defs.xml
- Ejemplo 9. header-footer-sidebar-layout.jsp
- Utilizar el patrón Composite con Struts Tiles
- Ejemplo 10. WEB-INF/tiles-defs.xml: Utilizar el patrón Composite
- Ejemplo 11. sidebar-layout.jsp
- Ejemplo 12. flags.jsp
- Ejemplo 13. sidebar-links.jsp
- Todo son composites hoy en día
- Tareas
- Tarea de la última vez
- Ejemplo H1. flags.jsp
- Ejemplo H2. WEB-INF/web.xml (Excerpt)
- Ejemplo H3. WEB-INF/struts-config.xml
- Ejemplo H4. WEB-INF/classes/actions/FlagAction.java
- Ejemplo H5. WEB-INF/web.xml (Extracto)
- Ejemplo H6. sidebar-links.jsp
- Ejemplo H7. header.jsp
- Ejemplo H8. content.jsp
- Ejemplo H9. footer.jsp
- Ejemplo H10. WEB-INF/classes/resources_en.properties
- Ejemplo H11. WEB-INF/classes/resources_zh.properties
- Correo electrónico
- Más información sobre este tema
El patrón Composite
Antes de sumergirnos en el patrón Composite, debo definir primero los objetos compuestos: objetos que contienen otros objetos; por ejemplo, un dibujo puede estar compuesto por primitivas gráficas, como líneas, círculos, rectángulos, texto, etc.
Los desarrolladores de Java necesitan el patrón Composite porque a menudo debemos manipular los objetos compuestos exactamente de la misma manera que manipulamos los objetos primitivos. Por ejemplo, las primitivas gráficas como las líneas o el texto deben ser dibujadas, movidas y redimensionadas. Pero también queremos realizar la misma operación en los compuestos, como los dibujos, que están formados por esas primitivas. Lo ideal sería realizar las operaciones sobre los objetos primitivos y los compuestos exactamente de la misma manera, sin distinguir entre ambos. Si tenemos que distinguir entre objetos primitivos y compuestos para realizar las mismas operaciones en esos dos tipos de objetos, nuestro código se volvería más complejo y más difícil de implementar, mantener y extender.
En Design Patterns, los autores describen el patrón Composite de la siguiente manera:
Componer objetos en estructuras de árbol para representar jerarquías parte-todo. Composite permite a los clientes tratar objetos individuales y composiciones de objetos de manera uniforme.
Implementar el patrón Composite es fácil. Las clases compuestas extienden una clase base que representa objetos primitivos. La figura 1 muestra un diagrama de clases que ilustra la estructura del patrón Composite.
En el diagrama de clases de la Figura 1, he utilizado nombres de clases de la discusión del patrón Composite de Design Pattern: Component
representa una clase base (o posiblemente una interfaz) para objetos primitivos, y Composite
representa una clase compuesta. Por ejemplo, la clase Component
podría representar una clase base para primitivas gráficas, mientras que la clase Composite
podría representar una clase Drawing
. La clase Leaf
de la Figura 1 representa un objeto primitivo concreto; por ejemplo, una clase Line
o una clase Text
. Los métodos Operation1()
y Operation2()
representan métodos específicos del dominio implementados por las clases Component
y Composite
.
La clase Composite
mantiene una colección de componentes. Normalmente, los métodos de Composite
se implementan iterando sobre esa colección e invocando el método apropiado para cada Component
de la colección. Por ejemplo, una clase Drawing
podría implementar su método draw()
así:
Por cada método implementado en la clase Component
, la clase Composite
implementa un método con la misma firma que itera sobre los componentes del compuesto, como ilustra el método draw()
mencionado anteriormente.
La clase Composite
extiende la clase Component
, por lo que puede pasar un compuesto a un método que espera un componente; por ejemplo, considere el siguiente 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();}
Al método anterior se le pasa un componente-ya sea un componente simple o un compuesto-entonces invoca el método draw()
de ese componente. Debido a que la clase Composite
extiende a Component
, el método repaint()
no necesita distinguir entre componentes y compuestos-simplemente invoca el método draw()
para el componente (o compuesto).
El diagrama de clase del patrón Composite de la Figura 1 ilustra un problema con el patrón: debes distinguir entre componentes y compuestos cuando haces referencia a un Component
, y debes invocar un método específico del compuesto, como addComponent()
. Normalmente se cumple este requisito añadiendo un método, como isComposite()
, a la clase Component
. Ese método devuelve false
para los componentes y se sobrescribe en la clase Composite
para devolver true
. Además, también debe lanzar la referencia Component
a una instancia Composite
, así:
Nota que al método addComponent()
se le pasa una referencia Component
, que puede ser un componente primitivo o un compuesto. Dado que ese componente puede ser un compuesto, se pueden componer componentes en una estructura de árbol, como se indica en la cita mencionada de Design Patterns.
La figura 2 muestra una implementación alternativa del patrón Composite.
Si implementas el patrón Composite de la Figura 2, no tienes que distinguir nunca entre componentes y compuestos, y no tienes que lanzar una referencia Component
a una instancia Composite
. Así que el fragmento de código anterior se reduce a una sola línea:
...component.addComponent(someComponentThatCouldBeAComposite);...
Pero, si la referencia Component
en el fragmento de código anterior no se refiere a un Composite
, ¿qué debe hacer el addComponent()
? Ese es un importante punto de controversia con la implementación del patrón Composite de la Figura 2. Dado que los componentes primitivos no contienen otros componentes, añadir un componente a otro no tiene sentido, por lo que el método Component.addComponent()
puede fallar silenciosamente o lanzar una excepción. Normalmente, añadir un componente a otro componente primitivo se considera un error, por lo que lanzar una excepción es quizás el mejor curso de acción.
Entonces, ¿qué implementación del patrón Composite -la de la Figura 1 o la de la Figura 2- funciona mejor? Ese es siempre un tema de gran debate entre los implementadores del patrón Composite; Design Patterns prefiere la implementación de la Figura 2 porque nunca hay que distinguir entre componentes y contenedores, y nunca hay que realizar un cast. Personalmente, prefiero la implementación de la Figura 1, porque tengo una fuerte aversión a implementar métodos en una clase que no tienen sentido para ese tipo de objeto.
Ahora que entiendes el patrón Composite y cómo puedes implementarlo, vamos a examinar un ejemplo de patrón Composite con el framework Apache Struts JavaServer Pages (JSP).
El patrón Composite y Struts Tiles
El framework Apache Struts incluye una librería de etiquetas JSP, conocida como Tiles, que permite componer una página web a partir de múltiples JSPs. Tiles es en realidad una implementación del patrón CompositeView de J2EE (Java 2 Platform, Enterprise Edition), basado a su vez en el patrón Composite de Design Patterns. Antes de hablar de la relevancia del patrón Composite para la biblioteca de etiquetas Tiles, revisemos primero la razón de ser de Tiles, y cómo se utiliza. Si ya está familiarizado con Struts Tiles, puede hojear las siguientes secciones y comenzar a leer en «Use the Composite Pattern with Struts Tiles.»
Nota: Puede leer más sobre el patrón J2EE CompositeView en mi artículo «Web Application Components Made Easy with Composite View» (JavaWorld, diciembre de 2001).
Los diseñadores suelen construir páginas web con un conjunto de regiones discretas; por ejemplo, la página web de la Figura 3 comprende una barra lateral, una cabecera, una región de contenido y un pie de página.
Los sitios web suelen incluir varias páginas web con diseños idénticos, como el diseño de barra lateral/cabecera/contenido/pie de página de la Figura 3. Struts Tiles permite reutilizar tanto el contenido como el diseño entre varias páginas web. Antes de hablar de esa reutilización, veamos cómo se implementa tradicionalmente el diseño de la Figura 3 sólo con HTML.
Implementa diseños complejos a mano
El ejemplo 1 muestra cómo puedes implementar la página web de la Figura 3 con HTML:
Ejemplo 1. Un diseño complejo implementado a mano
El JSP anterior tiene dos grandes inconvenientes: En primer lugar, el contenido de la página está incrustado en el JSP, por lo que no se puede reutilizar nada de él, aunque es probable que la barra lateral, la cabecera y el pie de página sean los mismos en muchas páginas web. En segundo lugar, el diseño de la página también está incrustado en esa JSP, por lo que tampoco se puede reutilizar aunque muchas otras páginas web del mismo sitio web utilicen el mismo diseño. Podemos utilizar la acción <jsp:include>
para remediar el primer inconveniente, tal y como se explica a continuación.
Implementar diseños complejos con JSP includes
El ejemplo 2 muestra una implementación de la página web de la figura 3 que utiliza <jsp:include>
:
Ejemplo 2. Un diseño complejo implementado con JSP incluye
La JSP anterior incluye el contenido de otras JSP con <jsp:include>
. Como he encapsulado ese contenido en JSPs separados, puedes reutilizarlo para otras páginas web:
Para completar, he enumerado a continuación las JSPs incluidas por la JSP precedente:
Ejemplo 4. header.jsp
<font size='6'>Welcome to Sabreware, Inc.</font><hr>
Ejemplo 5. content.jsp
<font size='4'>Page-specific content goes here</font>
<hr>Thanks for stopping by!
Aunque la JSP del Ejemplo 2 utiliza <jsp:include>
para reutilizar el contenido, no puedes reutilizar el diseño de la página porque está codificado en esa JSP. Struts Tiles le permite reutilizar tanto el contenido como el diseño, como se ilustra en la siguiente sección.
Implementar diseños complejos con Struts Tiles
El ejemplo 7 muestra la página web de la Figura 3 implementada con Struts Tiles:
Ejemplo 7. Utilizar Struts Tiles para encapsular el layout
La JSP anterior utiliza la etiqueta <tiles:insert>
para crear la JSP de la Figura 3. Esa JSP está definida por una definición de tiles llamada sidebar-header-footer-definition
. Esa definición reside en el archivo de configuración de Tiles, que en este caso es WEB-INF/tiles-defs.xml
, listado en el Ejemplo 8:
Ejemplo 8. WEB-INF/tiles-defs.xml
La definición de Tiles anterior especifica el diseño de la página, encapsulado en header-footer-sidebar-layout.jsp
, y el contenido de la página, encapsulado en sidebar.jsp
, header.jsp
, content.jsp
y footer.jsp
, como se indica en los Ejemplos 3-6. El ejemplo 9 enumera la JSP que define el diseño-header-footer-sidebar-layout.jsp
:
La JSP anterior encapsula el diseño e inserta el contenido según los valores especificados para las regiones de la barra lateral, el editor, el contenido y el pie de página en el archivo de definición de Tiles, facilitando así la reutilización tanto del contenido como del diseño. Por ejemplo, puedes definir otra definición de Tiles con el mismo diseño, la misma barra lateral, el mismo editor y el mismo pie de página, pero con diferente contenido:
Para crear la JSP definida por la definición de tiles a-different-sidebar-header-footer-definition
, utilizas la etiqueta <tiles:insert>
, así:
Gracias a Struts Tiles, puedes reutilizar tanto el contenido como el diseño, lo que resulta muy valioso para los sitios web con muchas JSP que comparten el diseño y algunos contenidos. Pero si miras de cerca el código de los Ejemplos 7-9, te darás cuenta de que el diseño de la región de la barra lateral está codificado en sidebar.jsp
, que aparece en el Ejemplo 3. Esto significa que no puedes reutilizar ese diseño. Afortunadamente, la biblioteca de etiquetas Tiles implementa el patrón Composite, que nos permite especificar una definición de tiles -en lugar de un JSP- para una región. En la siguiente sección, explico cómo utilizar esa implementación del patrón Composite.
Utilizar el patrón Composite con Struts Tiles
Struts Tiles implementa el patrón Composite, donde la clase Component
está representada por JSPs y la clase Composite
está representada por una definición de Tiles. Esta implementación permite especificar tanto una JSP (un componente) como una definición de Tiles (un compuesto) como contenido de la región de una JSP. El ejemplo 10 ilustra esta característica:
Ejemplo 10. WEB-INF/tiles-defs.xml: Utilizar el patrón Composite
El archivo de configuración de Tiles anterior define dos definiciones de Tiles: sidebar-definition
y sidebar-header-footer-definition
. El sidebar-definition
se especifica como el valor de la región de la barra lateral en el sidebar-header-footer-definition
. Se puede especificar así porque Tiles implementa el patrón Composite dejando que Tiles especifique una definición (un Composite
que es una colección de JSP) donde normalmente se especificaría un solo JSP (que es un Component
).
El diseño de la barra lateral está encapsulado en el sidebar-layout.jsp
:
El Ejemplo 12 enumera flags.jsp
, especificado como el contenido para la región top
de la barra lateral, y el Ejemplo 13 enumera sidebar-links.jsp
, especificado como la región bottom
de la barra lateral:
Ejemplo 12. flags.jsp
Ahora el sidebar-definition
puede definir otras regiones con un componente superior e inferior, aunque probablemente deberías renombrar esa definición a algo más genérico como top-bottom-definition
.
Todo son composites hoy en día
El patrón Composite es popular entre los frameworks de presentación, como Swing y Struts, porque permite anidar contenedores tratando los componentes y sus contenedores exactamente igual. Struts Tiles utiliza el patrón Composite para especificar una simple JSP o una definición de Tiles -que es una colección de JSPs- como contenido de un tile. Esta es una poderosa capacidad que facilita la gestión de grandes sitios web con diferentes diseños.
La sección «Deberes de la última vez» que aparece a continuación amplía la discusión de este artículo internacionalizando la aplicación anterior con una acción de Struts y la JSP Standard Tag Library (JSTL).
Tareas
Discuta cómo Swing implementa el patrón compuesto con las clases Component
y Container
.
Tarea de la última vez
Su última tarea le pidió que descargara Struts de http://jakarta.apache.org/struts/index.html e implementara su propia clase de acción de Struts.
Para esta tarea, decidí implementar una acción de Struts junto con la biblioteca de etiquetas estándar de JSP (JSTL) para internacionalizar la aplicación web del Ejemplo 1. Aunque Struts proporciona la infraestructura necesaria para internacionalizar sus aplicaciones web, debe utilizar JSTL para esa tarea, porque JSTL es un estándar. En algún momento, las capacidades de internacionalización de Struts probablemente serán obsoletas o integradas con JSTL.
Después de internacionalizar la aplicación Web del Ejemplo 1 con una acción de Struts y JSTL, localicé esa aplicación para el chino. La figura H1 ilustra el resultado.
Nota: No sé ni una sola palabra de chino, y mucho menos cómo escribir el idioma, así que el chino de la figura H1 está fabricado con cadenas Unicode arbitrarias. Pero se ve bien, a pesar de todo.
Nota que he especificado los atributos href
en el flags.jsp
del Ejemplo 12 como cadenas vacías, así que cuando haces clic en las banderas, el contenedor del servlet recarga el JSP actual. Por lo tanto, el primer paso para internacionalizar la aplicación web es especificar una URL para esos atributos, como se indica en el ejemplo H1:
Ejemplo H1. flags.jsp
La URL para cada atributo href
es la misma: flags.do
. A esa URL se le añaden dos parámetros de petición: uno para la configuración regional correspondiente a la bandera y otro que representa la ruta del JSP actual. Este último parámetro de petición se obtiene invocando el método Http.ServletRequest.getServletPath()
.
En el descriptor de despliegue de la aplicación, he mapeado todas las URLs que terminan en .do
al servlet de acción de Struts, así:
Ejemplo H2. WEB-INF/web.xml (Excerpt)
Nota: Consulte mi artículo «Take Command of Your Software» (JavaWorld, junio de 2002) para obtener más información sobre Struts y el servlet de acción de Struts.
A continuación, asigné la ruta /flags
a la acción de Struts actions.FlagAction
en el archivo de configuración de Struts, que aparece en el Ejemplo H3:
Ejemplo H3. WEB-INF/struts-config.xml
Debido a ese mapeo, la URL flags.do
hace que el servlet de la acción de Struts invoque el método actions.FlagAction.execute()
; por lo tanto, al hacer clic en una bandera se invocará el método actions.FlagAction.execute()
. El ejemplo H4 lista la clase actions.FlagAction
:
Ejemplo H4. WEB-INF/classes/actions/FlagAction.java
El método actions.FlagAction.execute()
obtiene una referencia al parámetro de solicitud locale
y pasa ese valor al método set()
de la clase JSTL Config
. Ese método almacena la cadena que representa la configuración regional en el parámetro de configuración FMT_LOCALE
, que JSTL utiliza para localizar el texto y formatear los números, las monedas, los porcentajes y las fechas.
Ahora que he especificado una configuración regional para las acciones de internacionalización de JSTL, a continuación especifico un paquete de recursos en el descriptor de despliegue de la aplicación, un extracto del cual aparece en el Ejemplo H5:
Ejemplo H5. WEB-INF/web.xml (Extracto)
El nombre base del resource bundle resources
se especifica para el parámetro de inicialización del contexto javax.servlet.jsp.jstl.fmt.localizationContext
. Esto significa que JSTL buscará un archivo llamado resources_en.properties
cuando la configuración regional esté establecida en inglés británico (en-GB
) y resources_zh.properties
cuando la configuración regional esté establecida en chino (zh-ZH
).
Ahora que he especificado un paquete de recursos y una configuración regional para las acciones de internacionalización de JSTL, a continuación modifico los archivos JSP para que utilicen esas acciones, como demuestran los ejemplos H6-H9:
Ejemplo H7. header.jsp
Ejemplo H8. content.jsp
Los JSP de los Ejemplos H6-H9 utilizan la acción JSTL <fmt:message>
para extraer cadenas Unicode del paquete de recursos especificado en el descriptor de implementación. Los ejemplos H10 y H11 enumeran los paquetes de recursos para inglés y chino, respectivamente:
Ejemplo H10. WEB-INF/classes/resources_en.properties
Ejemplo H11. WEB-INF/classes/resources_zh.properties
Correo electrónico
En un correo electrónico que me envió, Joseph Friedman escribió:
En «Decorate Your Java Code» (JavaWorld, diciembre de 2001), usted escribe:
«El objeto que encierra -conocido como decorador- se ajusta a la interfaz del objeto que encierra, lo que permite utilizar el decorador como si fuera una instancia del objeto que encierra.»
Sin embargo, el código del ejemplo decora el
FileReader
con unLineNumberReader
y llama areadLine()
, que no está en la interfaz delFileReader
; por lo tanto, no está utilizando elLineNumberReader
de forma transparente.
Tanto el FileReader
como el LineNumberReader
se ajustan a la interfaz definida por la clase Reader
, que ambos extienden. La idea es que usted puede pasar el decorador a cualquier método que espera una referencia a un Reader
. Esos métodos permanecen felizmente ignorantes de las capacidades especiales de ese decorador, en este caso, la capacidad de leer archivos y producir números de línea. Sin embargo, si conoces esas capacidades especiales, puedes aprovecharlas.
El hecho de que el decorador posea (uno o más) métodos de los que carece el objeto decorado no viola en absoluto la intención del patrón Decorator; de hecho, esa es la característica central de ese patrón: añadir funcionalidad en tiempo de ejecución a un objeto decorando objetos recursivamente.
Si miras la página 173 de Design Patterns, verás esto:
Las subclases de Decorator son libres de añadir operaciones para una funcionalidad específica. Por ejemplo, la operación
ScrollTo
deScrollDecorator
permite que otros objetos se desplacen por la interfaz *si* saben que hay un objetoScrollDecorator
en la interfaz.
DavidGeary es el autor de Core JSTL Mastering the JSP Standard TagLibrary, que será publicado este otoño porPrentice-Hall y Sun Microsystems Press; Advanced JavaServer Pages (PrenticeHall, 2001; ISBN: 0130307041); y la serie Graphic Java (Sun MicrosystemsPress). David lleva 18 años desarrollando software orientado a objetos con numerosos lenguajes orientados a objetos. Desde la publicación del libro GOFDesign Patterns en 1994, David ha sido un activo defensor de los patrones de diseño, y ha utilizado e implementado patrones de diseño en Smalltalk, C++ y Java. En 1997, David comenzó a trabajar a tiempo completo como autor y conferenciante y consultor ocasional. David es miembro de los grupos de expertos que definen la biblioteca de etiquetas personalizadas estándar de JSP y JavaServer Faces, y contribuye al marco JSP de Apache Struts.