Patrones de diseño estructurales: Composite

Intensión del patrón

  • Componer objetos en estructuras de árbol para representar jerarquías totales o parciales. El patrón Composite permite a los clientes tratar objetos individuales y compuestos de manera uniforme.
  • Composición recursiva. "Los directorios contienen entradas y cada una de ellas puede ser un directorio".


Ejemplo de problema
Una aplicación necesita manipular una colección jerárquica de objetos "primitivos" y "compuestos". El procesamiento de un objeto primitivo se maneja de una manera y el procesamiento de un objeto compuesto se maneja de otra. El tener que consultar el "tipo" de cada objeto antes de intentar procesarlo, no es deseable.


Discusión
Definir una clase base abstracta (Component) que especifica el comportamiento que necesita ser ejercido de manera uniforme sobre todos los objetos Primitive y Composite. Las subclases de Primitive y las clases de Composite, deben estar fuera de la clase Component. Cada objeto Component se acopla por sí mismo sólo al tipo Component abstracto, ya que gestiona sus "hijos".
Éste patrón debe ser utilizado cada vez que se tenga "composiciones que contengan componentes y cada uno de ellos pueda ser un Composite".
Métodos de manejos de hijos (Ej. AddChild(), RemoveChild()) deberían normalmente ser definidos en la clase Composite. Por desgracia, el deseo de tratar a los objetos Primitive y Composite de manera uniforme requiere que dichos métodos sean movidos a la clase abstracta Component.
Ver la sección Opiniones más abajo de los temas "seguridad" vs "transparencia".

Estructura
Compuestos que contienen componentes, cada uno de los cuales podría ser un compuesto (Composite).



  • Menúes que contienen a otras opciones de menú, las cuales pueden ser también menúes.
  • Directorios que contienen archivos e incluso podrían contener a otros directorios.
  • Contenedores que contienen elementos y cada uno de ellos podría ser también un contenedor.


Ejemplo
El patrón Composite compone objetos en estructuras de tipo árbol y le permite a los clientes tratar con objetos individuales y compuestos de manera uniforme. Aunque el ejemplo es abstracto, las expresiones aritméticas son Composites. Una expresión aritmética consiste de un operando, un operador (+ - * /) y otro operando. El operando puede ser un número u otra expresión aritmética. Por lo tanto, 2 + 3 y ( 2 + 3 ) + ( 4 * 6 ) son ambas expresiones válidas.


Check list

  1. Asegurarse que el problema es de representación (parcial o total) de relaciones jerárquicas.
  2. Considerar la heurística: "Contenedores que contienen elementos, cada uno de los cuales podría ser un contenedor". Por ejemplo, "Assemblies que contienen componentes, cada uno de los cuales podría ser un assemblie". Dividir los conceptos de dominio en clases contenedoras y clases contenido.
  3. Crear una interfaz que oficie de "mínimo denominador común" que haga que los contenedores y los contenidos puedan ser intercambiables. Debería especificar el comportamiento que necesita ser ejercido de manera uniforme entre todos los objetos contenido y contenedor.
  4. Todas las clases Contenedor y Contenido implementan la interfaz.
  5. Todas las clases Contenedor y Contenido declaran una relación de uno a muchos con la interfaz.
  6. Las clases Contenedor aprovechan el polimorfismo para delegar en sus objetos Contenido.
  7. Métodos de manejos de hijos (Ej. AddChild()RemoveChild()) deberían normalmente ser definidos en la clase Composite. Por desgracia, el deseo de tratar a los objetos Leaf y Composite de manera uniforme requiere que dichos métodos sean movidos a la clase abstracta Component. Ver la sección Opiniones más abajo de los temas "seguridad" vs "transparencia".


Reglas de oro
Los patrones Composite y Decorator poseen diagramas estructurales similares, lo que refleja el hecho de que ambos se basan en la composición recursiva para organizar un número abierto de objetos.
El patrón Composite puede ser recorrido con el patrón Iterator. El patrón Visitor puede aplicar una operación sobre Composite. El patrón Composite podría usar Chain of Responsability para permitir el acceso global de los componentes a las propiedades a través de su padre. Como también podría utilizar al patrón Decorator para sobrescribir dichas propiedades en ciertas partes de la composición. El Observer es otro patrón que podría utilizar para "atar" la estructura de un objeto a otro y State para permitirle al componente cambiar su comportamiento según su estado cambie.
El patrón Composite permite componer un Mediator en piezas más pequeñas mediante la composición recursiva.
Decorator está diseñado para permitir agregar responsabilidades a objetos sin usar subclases, mientras que Composite hace foco en la representación en lugar del "embellecimiento". Estas intenciones son distintas pero complementarias. En consecuencia, ambos son usados a menudo en conjunto.
El patrón Flyweight es usado a menudo combinado con Composite para implementar nodos compartidos.

Opiniones
El punto de todo el patrón Composite es que puede ser tratado de manera atómica, como una hoja. Si se desea proveer un protocolo de iteración, estaría bien, pero estaría fuera del patrón. El corazón de este patrón es la habilitación para un cliente para realizar operaciones en un objeto sin la necesidad de conocer que hay mucho objetos en su interior.
Ser capaz de tratar colecciones heterogéneas de objetos de manera atómica (o de manera transparente) requiere que la interfaz de "manejo de hijos" sea definida en la raíz de la jerarquía de Composite (la clase abstracta Component). Sin embargo, esta opción "cuesta" seguridad, porque los clientes pueden intentar hacer cosas sin sentido como agregar o quitar objetos del contenedor de objetos. Por otro lado, si se "diseña por la seguridad", la interfaz de manejo de "hijos" es declarada en la clase Composite, y se pierde transparencia porque ahora contenedores y contenidos poseen diferentes interfaces.
Las implementaciones de Smalltalk del patrón Composite no suelen tener la interfaz para gestión de los componentes en la interfaz Component sino en la de Composite. Las implementaciones de C++ tienden a ponerla en la interfaz de  Component. Este es un hecho extremadamente interesante aunque nadie sabe exactamente el por qué.

Ejemplo de código en C#
using System;
using System.Collections;

class MainApp
{
  static void Main()
  {
    // Create a tree structure 
    Composite root = new Composite("root");
    root.Add(new Leaf("Leaf A"));
    root.Add(new Leaf("Leaf B"));

    Composite comp = new Composite("Composite X");
    comp.Add(new Leaf("Leaf XA"));
    comp.Add(new Leaf("Leaf XB"));

    root.Add(comp);
    root.Add(new Leaf("Leaf C"));

    // Add and remove a leaf 
    Leaf leaf = new Leaf("Leaf D");
    root.Add(leaf);
    root.Remove(leaf);

    // Recursively display tree 
    root.Display(1);

    // Wait for user 
    Console.Read();
  }
}

// "Component" 
abstract class Component
{
  protected string name;

  // Constructor 
  public Component(string name)
  {
    this.name = name;
  }

  public abstract void Add(Component c);
  public abstract void Remove(Component c);
  public abstract void Display(int depth);
}

// "Composite" 
class Composite : Component
{
  private ArrayList children = new ArrayList();

  // Constructor 
  public Composite(string name) : base(name) 
  {  
  }

  public override void Add(Component component)
  {
    children.Add(component);
  }

  public override void Remove(Component component)
  {
    children.Remove(component);
  }

  public override void Display(int depth)
  {
    Console.WriteLine(new String('-', depth) + name);

    // Recursively display child nodes 
    foreach (Component component in children)
    {
      component.Display(depth + 2);
    }
  }
}

// "Leaf" 
class Leaf : Component
{
  // Constructor 
  public Leaf(string name) : base(name) 
  {  
  }

  public override void Add(Component c)
  {
    Console.WriteLine("Cannot add to a leaf");
  }

  public override void Remove(Component c)
  {
    Console.WriteLine("Cannot remove from a leaf");
  }

  public override void Display(int depth)
  {
    Console.WriteLine(new String('-', depth) + name);
  }
}

0 comentarios:

Publicar un comentario en la entrada

Muchas gracias por leer el post y comentarlo.

 
Copyright 2009 Programación SOLIDa
BloggerTheme by BloggerThemes | Design by 9thsphere