Una mirada al patrón de diseño compuesto

El otro día estaba escuchando Car Talk de National Public Radio , una popular transmisión semanal durante la cual las personas que llaman hacen preguntas sobre sus vehículos. Antes de cada pausa del programa, los presentadores del programa piden a las personas 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. En general, a los seres humanos les resulta más fácil tratar con compuestos que con sus componentes individuales. Del mismo modo, cuando desarrolla software orientado a objetos, a menudo es conveniente manipular compuestos del mismo modo que manipula componentes individuales. Esa premisa representa el principio fundamental del patrón de diseño compuesto,el tema de este Java Design Patterns entrega.

El patrón compuesto

Antes de sumergirnos en el patrón compuesto, primero debo definir objetos compuestos: objetos que contienen otros objetos; por ejemplo, un dibujo puede estar compuesto de primitivas gráficas, como líneas, círculos, rectángulos, texto, etc.

Los desarrolladores de Java necesitan el patrón compuesto porque a menudo debemos manipular compuestos exactamente de la misma manera que manipulamos objetos primitivos. Por ejemplo, las primitivas gráficas como líneas o texto deben dibujarse, moverse y redimensionarse. Pero también queremos realizar la misma operación en compuestos, como dibujos, que se componen de esos primitivos. Idealmente, nos gustaría realizar operaciones tanto en objetos primitivos como en compuestos exactamente de la misma manera, sin distinguir entre los dos. Si debemos 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 compuesto de esta manera:

Componga objetos en estructuras de árbol para representar jerarquías de parte y todo. Composite permite a los clientes tratar objetos individuales y composiciones de objetos de manera uniforme.

Implementar el patrón compuesto 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 compuesto.

En el diagrama de clases de la Figura 1, utilicé nombres de clases de la discusión del patrón compuesto de Design Pattern : Componentrepresenta una clase base (o posiblemente una interfaz) para objetos primitivos y Compositerepresenta una clase compuesta. Por ejemplo, la Componentclase podría representar una clase base para primitivas gráficas, mientras que la Compositeclase podría representar una Drawingclase. La Leafclase de la Figura 1 representa un objeto primitivo concreto; por ejemplo, una Lineclase o una Textclase. Los métodos Operation1()y Operation2()representan métodos específicos de dominio implementados por las clases Componenty Composite.

La Compositeclase mantiene una colección de componentes. Normalmente, los Compositemétodos se implementan iterando sobre esa colección e invocando el método apropiado para cada uno Componentde la colección. Por ejemplo, una Drawingclase podría implementar su draw()método de esta manera:

// Este método es un método compuesto public void draw () {// Itere sobre los componentes para (int i = 0; i <getComponentCount (); ++ i) {// Obtenga una referencia al componente e invoque su dibujo método Componente componente = getComponent (i); componente.draw (); }}

Para cada método implementado en la Componentclase, la Compositeclase implementa un método con la misma firma que itera sobre los componentes del compuesto, como lo ilustra el draw()método enumerado anteriormente.

La Compositeclase extiende la Componentclase, por lo que puede pasar un compuesto a un método que espera un componente; por ejemplo, considere el siguiente método:

// Este método se implementa en una clase que no está relacionada con las // Clases Componente y Compuesto public void repaint (Componente componente) {// El componente puede ser un compuesto, pero como extiende // la clase Componente, este método no necesita // distinguir entre componentes y compuestos component.draw (); }

Al método anterior se le pasa un componente, ya sea un componente simple o un compuesto, luego invoca el draw()método de ese componente . Debido a que la Compositeclase se extiende Component, el repaint()método no necesita distinguir entre componentes y compuestos; simplemente invoca el draw()método para el componente (o compuesto).

El diagrama de clases de patrón compuesto de la Figura 1 ilustra un problema con el patrón: debe distinguir entre componentes y compuestos cuando hace referencia a Component, y debe invocar un método específico de compuesto, como addComponent(). Por lo general, cumple ese requisito agregando un método, como isComposite(), a la Componentclase. Ese método devuelve los falsecomponentes y se anula en la Compositeclase para volver true. Además, también debe convertir la Componentreferencia a una Compositeinstancia, como esta:

... if (component.isComposite ()) {Compuesto compuesto = (Compuesto) componente; composite.addComponent (someComponentThatCouldBeAComposite); } ...

Observe que al addComponent()método se le pasa una Componentreferencia, que puede ser un componente primitivo o un compuesto. Debido a que ese componente puede ser un compuesto, puede componer componentes en una estructura de árbol, como lo indica la cita mencionada anteriormente de Design Patterns .

La Figura 2 muestra una implementación alternativa de patrón compuesto.

Si implementa el patrón compuesto de la Figura 2, nunca tendrá que distinguir entre componentes y compuestos, y no tendrá que lanzar una Componentreferencia a una Compositeinstancia. Entonces, el fragmento de código enumerado anteriormente se reduce a una sola línea:

... componente.addComponent (someComponentThatCouldBeAComposite); ...

Pero, si la Componentreferencia en el fragmento de código anterior no se refiere a a Composite, ¿qué debería addComponent()hacer? Ese es un importante punto de discordia con la implementación del patrón compuesto de Figura 2. Debido a que los componentes primitivos no contienen otros componentes, agregar un componente a otro componente no tiene sentido, por lo que el Component.addComponent()método puede fallar silenciosamente o lanzar una excepción. Por lo general, agregar 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 de patrón compuesto, la de la Figura 1 o la de la Figura 2, funciona mejor? Ese es siempre un tema de gran debate entre los implementadores de patrones compuestos; Design Patterns prefiere la implementación de la Figura 2 porque nunca necesita distinguir entre componentes y contenedores, y nunca necesita realizar una conversión. 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 comprende el patrón compuesto y cómo puede implementarlo, examinemos un ejemplo de patrón compuesto con el marco de trabajo Apache Struts JavaServer Pages (JSP).

El patrón compuesto y baldosas de puntales

El marco Apache Struts incluye una biblioteca de etiquetas JSP, conocida como Tiles, que le permite componer una página web a partir de múltiples JSP. Tiles es en realidad una implementación del patrón CompositeView J2EE (Java 2 Platform, Enterprise Edition), que se basa en el patrón Design Patterns Composite. Antes de discutir la relevancia del patrón compuesto para la biblioteca de etiquetas de Tiles, primero revisemos el fundamento de Tiles y cómo lo usa. Si ya está familiarizado con Struts Tiles, puede hojear las siguientes secciones y comenzar a leer en "Use el patrón compuesto con Struts Tiles".

Nota: Puede leer más sobre el patrón J2EE CompositeView en mi artículo "Componentes de aplicaciones web simplificados con la vista compuesta" ( JavaWorld, diciembre de 2001).

Los diseñadores a menudo construyen páginas web con un conjunto de regiones discretas; por ejemplo, la página web de la Figura 3 comprende una barra lateral, un encabezado, una región de contenido y un pie de página.

Los sitios web a menudo incluyen varias páginas web con diseños idénticos, como el diseño de la barra lateral / encabezado / contenido / pie de página de la Figura 3. Struts Tiles le permite reutilizar tanto el contenido como el diseño entre varias páginas web. Antes de discutir esa reutilización, veamos cómo el diseño de la Figura 3 se implementa tradicionalmente solo con HTML.

Implementar diseños complejos a mano

El ejemplo 1 muestra cómo puede implementar la página web de la Figura 3 con HTML:

Ejemplo 1. Un diseño complejo implementado a mano

    Implementación de diseños complejos a mano <% - Una tabla muestra todo el contenido de esta página -%>
   
Enlaces

Casa

Productos

Descargas

libros blancos

Contáctenos

Bienvenido a Sabreware, Inc.
El contenido específico de la página va aquí

¡Gracias por pasar!

La JSP anterior tiene dos inconvenientes principales: en primer lugar, el contenido de la página está incrustado en la JSP, por lo que no se puede reutilizar, aunque es probable que la barra lateral, el encabezado 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 ese JSP, por lo que tampoco puede reutilizarlo aunque muchas otras páginas web del mismo sitio web utilicen el mismo diseño. Podemos usar la acción para remediar el primer inconveniente, como discuto 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 usa :