PATRÓN COMPOSITE

Clasificación:

Patrón Estructural.

Propósito:

Integra objetos complejos dentro de una estructura similando un árbol, con el que se representa una jerarquía parte-todo, permitiendo tratar objetos complejos individualmente  y las composiciones uniformemente, de manera tal  que independiza al cliente y obtine un conjunto de elementos, los cuales poseen un comportamiento similar en la forma se crean y adicionan al de los elementos simples.  Además se trabaja éste patrón con el fin de ahorrar espacio en memoria en objetos de comportamiento similar, pero cuando todos éstos tienen una única referencia al padre, se dificulta el objetivo.   

Intención:

Componer objetos en estructuras de árbol para representar jerarquías parte-todo. Compuesto permite a los clientes tratar cada uno de los objetos y composiciones de objetos de manera uniforme. 

Motivación :

A veces es necesario trabajar tanto con elementos simples como con colecciones, donde las colecciones son formadas por múltiples elementos simples o por otras colecciones. Un sencillo ejemplo de esto son las aplicaciones de dibujo, donde se tiene la posibilidad de crear elementos de dibujo simples (líneas, rectángulos, textos, etc.) y luego agrupar con el objetivo de operar con todo el conjunto, o composición, como si fuera una unidad. A su vez, las composiciones se pueden volver a agrupar con más elementos simples o con tras composiciones, de la misma manera las aplicaciones gráficas tienen componentes que pueden agruparse para formar componentes mayores (contenedores). De esta forma se obtienen estructuras de árbol con las cuales se puede operar.

 Aplicabilidad:

Para representar jerarquías de objetos parte-todo, para que los clientes puedan manejar indistintamente objetos individuales o composiciones de objetos  tratándolos en la estructura composite de manera uniforme.  Como los componentes pueden almacenar múltiples padres, puede resultar  una ambigüedad al recorrer la estructura hacia arriba.  Dependiendo de la aplicación puede ser necesario o no el ordenamiento de los hijos, cuando el ordenamiento es importante, uno debe diseñar cuidadosamente una interfaz para el manejo y el acceso de los hijos. El patrón Iterator puede ser de utilidad para este objetivo.  Si es necesario recorrer o buscar en las composiciones con frecuencia, la clase Composite puede almacenar en un cache cierta información sobre esta tarea

Estructura :

Los clientes usan la clase Componente para interaccionar con los objetos de la estructura composite. Si el recipiente es una Hoja la petición se maneja directamente. Si se trata de un Composite, se pasa a sus componentes hijos, pudiéndose realizar operaciones adicionales antes o después

Participantes: 

 • Component: 

   o Declara la interfaz que presentarán los objetos en la composición.    o Implementa el comportamiento predeterminado para todas las clases, según corresponda.

  Declara una interfaz para acceder y administrar sus componentes hijos.

  • Leaf: 

 o Representa los objetos simples. No poseen hijos.

 o Define el comportamiento explícito de cada objeto simple.

   Composite: 

 o Define el comportamiento de los objetos que tienen hijos.  o Almacena los objetos hijos.  o Implementa las operaciones referentes a los hijos. 

  Client: 

 o Maneja los objetos en la composición mediante la interfaz Component.

 

Colaboradores: 

Los clientes usan la interfaz Component para acceder a los objetos dentro de la composición. Si se trabaja contra un objeto simple, la petición se maneja directamente. Si se trabaja contra un objeto de composición, entonces, por lo general, la petición es dirigida a todos los hijos, en algunos casos, realizando algunas tareas previas y posteriores a la operación.

 

Consecuencias: 

  • Define jerarquías de clases que tienen objetos primitivos y objetos compuestos (composite) 
  • La composición puede ser recursiva.
  •  Hace el cliente simple.
  • Puede tratar la estructura y los objetos individuales uniformemente.
  • Facilita la adición de nuevas clases de componentes.
  • Puede hacer que el diseño sea demasiado general.
  • Hace más difícil restringir los componentes de un composite– Si se quiere hacer      que un composite sólo tenga    ciertos componentes hay que codificar las comprobaciones para que se realicen en tiempo de ejecución.

 

Ventajas: 

  1. Define la jerarquía de clase en base a objetos simples y a composiciones. Los objetos simples pueden ser agrupados en composiciones, y éstas a su vez en nuevas composiciones, y así recursivamente.
  2. El cliente puede esperar a un objeto simple o aceptar una composición, en cualquier lugar que se encuentre.
  3. El cliente se torna más simple. Los clientes pueden operar de forma uniforme con un objeto simple como con una composición. Los clientes por lo general desconocen (y es transparente para ellos) contra que tipo de objeto están trabajando.
  4. Simplifica el código del cliente, porque se eliminan líneas de código repetitivas, para distinguir en qué caso se encuentra.
  5. Facilita el agregado de nuevos componentes. Los nuevos objetos simples y los nuevos contenedores trabajarán de forma automática con el resto de la estructura. Los clientes no deben ser modificados.
  6. Se trabaja con un diseño más general. 

Desventajas: 

La facilidad con la cual se pueden agregar objetos a una composición complica el poder restringir qué objetos pueden pertenecer a una composición determinada. Algunas veces uno desea composiciones que sólo posean ciertos tipos de elementos. Con este patrón uno no puede valerse de la estructura para asegurar las restricciones. La alternativa es realizar chequeos en tiempo de ejecución. Implementación

Referencia explícita al padre. Mantener las referencias de los hijos a su padre puede simplificar el recorrido y el manejo de la composición. Tener las referencias al padre también ayuda para respetar el patrón Chain of Responsibility. El mejor lugar para definir la referencia al padre es en la clase abstracta Component. Tanto Leaf como Composite la inferirán. 

Maximizando la interfaz de Component. Para lograr esto la clase Component debe definir la mayor cantidad de operaciones en común para las clases Composite (composiciones) y Leaf (simples) como sea posible. La clase Component usualmente provee las implementaciones predefinidas y tanto Composite como Leaf las sobrecargarán.

Esto puede llegar a estar en contradicción con el principio de diseño de la jerarquía de clases, en donde se dice que solo se deben definir operaciones con relevancia para las subclases. Hay operaciones en Component que no tienen ningún significado en Leaf. 

Con un poco de creatividad se puede hacer que las operaciones que sólo tenían significado para los tipo Composite puedan ser implementadas para todos los componentes definiéndola en Component. Por ejemplo la interfaz para acceder a los hijos es fundamental para Composite e innecesaria para Leaf. Pero si observamos a un objeto sencillo como si fuera un componente que nunca tiene hijos, podremos definir el comportamiento predefinido. Las clases Composite tendrán su propia implementación.

Declarando las operaciones de manejo de hijo. Si bien la clase Component implementa las operaciones de manejo de hijos, es importante notar que sólo son definidas en las clases Composite. La cuestión es, si estas operaciones deben ser creadas en Component, y de esta manera darles importancia en Leaf, o sólo declararlas para las Composite.

La decisión hace que se opte entre seguridad y transparencia. Transparencia en el caso de que se use la misma interfaz para todos los componentes. Seguridad en el caso de que no se declaren las operaciones de manejo de hijos para aquellas clases que por naturaleza nunca tendrán hijos.

Para ofrecer transparencia se debe definir el comportamiento predefinido para las  operaciones de manejo de hijos. Una alternativa es que en la clase base este definido para todas estas operaciones el envío de una excepción. Si se le asignara algún comportamiento, por lo menos nulo, podría prestar a confusiones. Además de esta manera Leaf sólo debe sobrecargar las operaciones que tengan importancia. En tanto para Composite será necesaria la sobrecarga de las operaciones de manejo de hijos.

Uno puede estar tentado a definir una lista de Components en Component, pero esto tiene la desventaja de una sobrecarga para todas las clases Leaf que nunca utilizarían tal repositorio. Además puede presentarse el caso de tener varias clases Composite en donde se deseé especializar el comportamiento del repositorio por medio de la utilización de diferentes estructuras.

Las opciones más comunes para almacenar hijos son listas, árboles, vectores (“arrays”) y “hash tables”. La elección depende de la eficiencia. En realidad, ni siquiera es necesario utilizar estas estructuras cotidianas. A veces los Composite tienen una variable por cada hijo, aunque esto requiere un trabajo adicional para el manejo de los mismos.

Implementación:

 El siguiente ejemplo muestra su funcionalidad, puesto que se permite crear compuesto de moléculas, conde cada molécula com mínimo un átomo de oxigeno posee o adiere más moleculas de hidrógeno, creando niveles en su diseño.

package Logica;

/**

 *

 * @author Katherin

 */

public abstract class Componente {

    protected String nombre;

    protected String hojaInfor;

    protected String informacion;

    protected String informacion2;

    public Componente(String nombre) {

        this.nombre = nombre;

    }   

    abstract public void agregar(Componente c);

    abstract public void remover(Componente c);

    abstract public void mostrar(int prof);

     abstract public String getInformacion1();

     abstract public String getInformacion2();

}

package Logica;
import java.util.ArrayList;
/**
 *
 * @author Katherin
 */
public class Compuesto extends Componente {
    private ArrayList hijo = new ArrayList();
    public Compuesto(String name) {
        super(name);
    }
    public int getSize() {
        return hijo.size();
    }
    @Override
    public void agregar(Componente componente) {
        hijo.add(componente);
    }
    @Override
    public void remover(Componente componente) {
        hijo.remove(componente);
    }
    @Override
    public void mostrar(int prof) {
        informacion2 = this.getInformacion1() + "\n";
        for (int i = 0; i <>
            hijo.get(i).mostrar(prof + 1);
            informacion2 = informacion2 + hijo.get(i).getInformacion2() + "\n";
        }
    }
    @Override
    public String getInformacion2() {
        return informacion2;
    }
    @Override
    public String getInformacion1() {
        return nombre;
    }
}
package Logica;
/**
 *
 * @author Katherin
 */
public class Hoja extends Componente {
    public Hoja(String nombre) {
        super(nombre);
    }
    public String getInformacion2() {
        return hojaInfor;
    }
  
    public void agregar(Componente c) {
        System.out.println("Se ha adicionado un componente");
    }
    public void remover(Componente c) {
        System.out.println("Se ha removido el componente");
    }
    public void mostrar(int prof) {
        hojaInfor = "  - " +  nombre;
    }
    @Override
    public String getInformacion1() {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
package Logica;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.*;
/**
 *
 * @author Katherin
 */
public class Laboratorio extends JFrame implements ActionListener {
    private JTextArea tablero;
    private String texto;
    private JButton hidrogeno;
    private JButton oxigeno;
    private JButton combinarMoleculas;
    private JButton nuevoCompuesto;
    private JLabel contOxigeno;
    private JLabel contHidrogeno;
    private String nombre;
    private int numHidrogenos = 0;
    private int hidrogenosNivel = 0;
    private Compuesto temp;
    private Compuesto raiz;
    private int contador = 0;
    private int combinacion = 0;
    private boolean inicio = false;
    private int i;
    private int numOxigeno = 0;
    private boolean mostrar = false;
    public Laboratorio() {
        this.setLayout(null);
        this.setTitle("Laboratorio de Moléculas");
        this.setSize(400, 500);
        this.setLocation(100, 100);
        tablero = new JTextArea();
        tablero.setSize(350, 300);
        tablero.setLocation(10, 10);
        tablero.setText("");
        tablero.setEditable(false);
        add(tablero);
        JScrollPane areaScrollPane = new JScrollPane(tablero);
        areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        areaScrollPane.setSize(350, 300);
        areaScrollPane.setLocation(10, 10);
        this.getContentPane().add(areaScrollPane);
        oxigeno = new JButton();
        oxigeno.setSize(100, 20);
        oxigeno.setLocation(20, 400);
        oxigeno.setText("Oxigeno");
        oxigeno.addActionListener(this);
        add(oxigeno);
        contOxigeno = new JLabel();
        contOxigeno.setSize(20, 10);
        contOxigeno.setLocation(60, 380);
        contOxigeno.setText("");
        contOxigeno.setVisible(true);
        add(contOxigeno);
        hidrogeno = new JButton();
        hidrogeno.setSize(100, 20);
        hidrogeno.setLocation(140, 400);
        hidrogeno.setText("Hidrogeno");
        hidrogeno.addActionListener(this);
        add(hidrogeno);
        contHidrogeno = new JLabel();
        contHidrogeno.setSize(20, 10);
        contHidrogeno.setLocation(180, 380);
        contHidrogeno.setText("");
        contHidrogeno.setVisible(true);
        add(contHidrogeno);
        combinarMoleculas = new JButton();
        combinarMoleculas.setSize(100, 20);
        combinarMoleculas.setLocation(260, 400);
        combinarMoleculas.setText("Combinar");
        combinarMoleculas.addActionListener(this);
        add(combinarMoleculas);
        nuevoCompuesto = new JButton();
        nuevoCompuesto.setSize(150, 20);
        nuevoCompuesto.setLocation(110, 350);
        nuevoCompuesto.setText("Nuevo Compuesto");
        nuevoCompuesto.addActionListener(this);
        add(nuevoCompuesto);
        addWindowListener(new Cierre());
    }
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == oxigeno) {
            if (combinacion == 0 && inicio == false) {
                JOptionPane.showMessageDialog(null, "Primero debe crear un objeto compuesto");
            }
            if (combinacion == 0 && inicio == true) {
                contador++;
                numOxigeno++;
                nombre = nombre + contador;
                raiz = new Compuesto(nombre);
                inicio = false;
            }
            if (combinacion == 1 && inicio == false) {
                contador++;
                nombre = nombre + contador;
                temp = new Compuesto(nombre);
                numOxigeno++;
            }
        }
        if (e.getSource() == hidrogeno) {
            if (contador != 0 && numOxigeno != 0) {
                ++hidrogenosNivel;
                ++numHidrogenos;
                contHidrogeno.setText("" + hidrogenosNivel);
                if (combinacion == 0) {
                    nombre = "Hidrogeno " + numHidrogenos;
                    raiz.agregar(new Hoja(nombre));
                    mostrar = true;
                } else {
                    nombre = "Hidrogeno " + hidrogenosNivel;
                    temp.agregar(new Hoja(nombre));
                    mostrar = true;
                }
            } else {
                JOptionPane.showMessageDialog(null, "El compuesto debe tener almenos un átomo de oxígeno");
            }
        }
        if (e.getSource() == nuevoCompuesto) {
            contHidrogeno.setText("");
            hidrogenosNivel = 0;
            mostrar = false;
            nombre = JOptionPane.showInputDialog("Nombre para la molécula");
            nombre = "Oxigeno " + nombre + " nivel: ";
            if (contador == 0) {
                inicio = true;
            } else {
                combinacion = 1;
                numOxigeno = 0;
            }
        }
        if (e.getSource() == combinarMoleculas) {
            if (combinacion == 1) {
                mostrar = true;
                contHidrogeno.setText("");
                raiz.agregar(temp);
                temp = null;
                combinacion = 0;
            } else {
                JOptionPane.showMessageDialog(null, "Debe terner un compuesto creado para adicionarlo.");
            }
        }
        if (contador != 0 && mostrar == true) {
            texto = "";
            for (i = 0; i <>
                raiz.mostrar(i);
                tablero.setText(raiz.getInformacion2() + "\n");
            }
        }
    }
}
class Cierre extends WindowAdapter {
    @Override
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
}
package interfaz;
import Logica.*;
/**
 *
 * @author Katherin
 */
public class Main {
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Laboratorio lab=new Laboratorio();
        lab.setVisible(true);
    }
}

Usos conocidos:

Ejemplos de los compuestos modelo se puede encontrar en casi todos orientados a objetos sistemas. El original de la clase Ver Smalltalk Modelo / Vista / Controlador [KP88] es un compuesto, y casi todas las herramientas de interfaz de usuario o marco ha seguido en sus pasos, incluyendo ET ++ (con sus VObjects [WGM88]) y entrevistas (Estilos  [ICL 92], Gráficos [VL88], y, glifos [CL90]). Es interesante observar que la Ver original de Modelo / Vista / Controlador había un conjunto de subviews; en otras palabras,  Ver es a la vez el componente de clase y la clase de compuestos. Liberación de 4,0 Smalltalk-80 Modelo / Vista / Controlador VisualComponent con una clase que ha 

Ver CompositeView y subclases.  

Patrones Relacionados: 

  • Chain of Responsibility: Usualmente usado en el vínculo componente-padre.
  • Decorator: Cuando son usados en conjunto, tendrán una clase padre en común. Por lo tanto Decorator tendrá que soportar la interfaz de un componente (Add, Remove y GetChild).
  • Flyweight: permite compartir componentes, pero ya no pueden tener una referencia al padre.
  • Iterator: puede ser usado para recorrer las composiciones.
  • Visitor: localiza operaciones y comportamiento que, de otra manera, serian distribuidos a través de las clases Composite y Leaf.

 Referencias:

  •  PDF-Departamento de Sistemas Informáticos y Programación Curso de doctorado 1999 -2000 Patrones de   diseño orientado a objetos.
  •  DesIgn Patterns: Elements of Reusable Object-Oriented Software Gamma, Helm, Johnson,  Vlissides Editorial   Addison-Wesley.
  • Diseño y Programación Orientado a Objetos. Capítulo 4. Ingeniería Informática Ingeniería Técnica de Informática  de Sistemas y Gestión Optativa. http://www.info-ab.uclm.es/asignaturas/42579
  •  http://www.universala.net/blog/archive/2008/08/19/patron-composite.aspx

No hay comentarios:

Publicar un comentario