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.
o 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:
- 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.
- El cliente puede esperar a un objeto simple o aceptar una composición, en cualquier lugar que se encuentre.
- 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.
- Simplifica el código del cliente, porque se eliminan líneas de código repetitivas, para distinguir en qué caso se encuentra.
- 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.
- 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