Composite

RECU-0184 (Recurso Patrón)

Descripción

El patrón Composite sirve para construir objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol. Esto simplifica el tratamiento de los objetos creados, ya que, al poseer todos ellos una interfaz común, se tratan todos de la misma manera.

Nombre

También denominado Compositor

Clasificación

Patrón estructural

Motivación

Pensamos en una aplicación gráfica de componentes sencillos (lineas, texto, etc) Imaginemos que necesitamos crear una serie de clases para guardar información acerca de una figuras geométricas. Se crearán las clases Círculo, Cuadrado y Triángulo, que heredarán todas de la clase Figura y que implementarán el método pintar().  Además, se necesita  poder tratar también grupos de imágenes, ya que  nuestro programa permite seleccionar varias de estas figuras a la vez para moverlas por la pantalla. Para la implementación de estos grupos de Figuras podríamos caer en la tentación de crear una clase particular separada de las anteriores llamada GrupoDeImágenes, también con un método pintar().

Podríamos pensar la idea de separar en clases privadas componentes (figuras) y contenedores (grupos) pero tiene el problema de que, para cada uno de los dos tipo de objeto, el método pintar() tendrá una implementación diferente, aumentando la complejidad del sistema. Lo lógico es crear una clase abstracta que represente componentes y contenedores de la cual todas heredan, y que define sus operaciones. Por ejemplo, en este caso, se podría crear un clase Gráfico de la que hereden tanto la clase Figura como la clase GrupoDeImagenes, y que incluya el método pintar(). Además, ésta última tendría una relación todo-parte de multiplicidad con Gráfico: un GrupoDeImágenes contendría varios Gráficos, ya fuesen éstos Cuadrados, Triángulos, u otras clases GrupoDeImágenes

De esta forma, el patrón Composite da una solución elegante a este problema, de la que además resulta en una implementación más sencilla.

Aplicabilidad

  • Se quiere realizar jerarquías de objetos del tipo "todo-parte"
  • Se quiere ser capaz de ignorar la diferencia entre los objetos individuales y composiciones de objetos. Los clientes tratarán a todos los objetos de la estructura compuesta uniformemente.

Estructura

El patrón se representa gráficamente de la siguiente manera

Participantes

  • Componente: Define la interfaz común para los objetos de la composición. Ademas, define la interfaz para acceder y gestionar los hijos. Implementa un comportamiento por defecto común a las subclases
  • Hoja: Representa los objetos sin hijos de la composición. Define el comportamiento de los mismos.
  • Composite: Define el comportamiento para los componentes que tienen hijos. Almacena los componentes con hijos e implementa las operaciones para la gestión de hijos
  • Cliente: Manipula los objetos de la composición a través de la interfaz

Colaboraciones

Cliente usa el interfaz de Componente para interactuar con objetos en la estructura de Composite. Si el receptor es una Hoja, entonces la petición es manejada directamente. Si el receptor es un Composite, lanza la petición a sus hijos.

Consecuencias

  • Define jerarquías de clases hechas de objetos primitivos y compuestos. Si el código cliente espera un objeto simple, puede recibir también uno compuesto.
  • Simplifica el cliente, ya que los objetos simples y compuestos se tratan homogéneamente.
  • Facilita la incorporación de nuevos tipos de componentes.
  • Puede hacer el diseño demasiado general.

Implementación

Algunas recomendaciones para la implementación del patrón son las siguientes.

  • Referencias explicitas a los padres. Con ello se simplifica algunas operaciones de la estructura compuesta. Lo mejor es definirlas en la clase Componente. Gestionarlas al añadir/eliminar elementos de un Composite.
  • Compartir Componentes: Es muy útil por el ahorro de memoria que supone. La gestión del componente con varios padres puede llegar a ser compleja
  • Maximizar la interfaz del componente: Dar un comportamiento por defecto que sobrescribirán las subclases.
  • Orden de los hijos: En ocasiones los hijos presentan un orden, y hay que tenerlo en cuenta para la implementación
  • Declaración de las operaciones de manejo de hijos: Definirlas en la raíz Componente y darle una implementación por defecto permite aumentar la transparencia, pero se pierde seguridad (¿Cómo evitar que añada/elimine objetos a una hoja?). Si las definimos en la clase Composite se obtiene más seguridad pero se pierde transparencia al no existir una interfaz uniforme.

Código de ejemplo

public abstract class Componente
{
    protected String nombre;
    public Componente (String nombre)
    {
        this.nombre = nombre;
    }
    abstract public void Agregar(Componente c);
    abstract public void Remover(Componente c);
    abstract public void Mostrar(int profundidad);
}
class Composite extends Componente
{
    private ArrayList<Componente> hijo = new ArrayList<Componente>();
    public Composite (String name)
    {
        super(name);
    }
    public void Agregar(Componente componente)
    {
        hijo.add(componente);
    }
    public void Remover(Componente componente)
    {
        hijo.remove(componente);
    }
    public void Mostrar(int profundidad)
    {
        System.out.println(nombre + " nivel: " + profundidad);
        for (int i = 0; i < hijo.size(); i++)
            hijo.get(i).Mostrar(profundidad + 1);
    }
}
class Hoja extends Componente
{
    public Hoja (String nombre)
    {
        super(nombre);
    }
    public void Agregar(Componente c)
    {
        System.out.println("no se puede agregar la hoja");
    }
    public void Remover(Componente c)
    {
        System.out.println("no se puede quitar la hoja");
    }
    public void Mostrar(int depth)
    {
        System.out.println('-' + "" + nombre);
    }
}
public class Client
{
    public static void main(String[] args)
    {
        Composite raiz = new Composite("root");
        raiz.Agregar(new Hoja("hoja A"));
        raiz.Agregar(new Hoja("hoja B"));
        Composite comp = new Composite("compuesto X");
        comp.Agregar(new Hoja("hoja XA"));
        comp.Agregar(new Hoja("hoja XB"));
        raiz.Agregar(comp);
        raiz.Agregar(new Hoja("hoja C"));
        Hoja l = new Hoja("hoja D");
        raiz.Agregar(l);
        raiz.Remover(l);
        raiz.Mostrar(1);
    }
}

Usos conocidos

Las clases java.awt.Component (Componente), java.awt.Container (Contenedor), java.awt.Panel (Contenedor concreto), java.awt.Button (Boton).

Contenidos relacionados

Recursos
Área: Desarrollo » Patrones de Diseño
Código Título Tipo Carácter
RECU-0013 Patrones de diseño Ficha Recomendado