Tras un breve (quizás no tan breve) periodo de tiempo, vamos a continuar con la serie de artículos relacionados con los patrones de diseño. El siguiente patrón que vamos a ver es el patrón “Composite”. El objetivo de este patrón es el de montar los objetos en una estructura de árbol para representarlos de una forma jerarquizada. De este modo, se podrán manejar de forma similar y uniforme, tanto los objetos individuales como los objetos compuestos.
Este patrón es aplicable en situaciones es las que se necesita trabajar simultaneamente con elementos simples y colecciones que contienen elementos simples u otras colecciones, obteniendo de esta forma la estructura de árbol comentada. Un ejemplo muy utilizado para hacer ver la necesidad y aplicabilidad de este patrón es la implementación de un editor gráfico en el que podemos encontrar multitud de figuras y, además, escenas compuestas por muchas figuras. Siendo las figuras los objectos simples y las escenas colecciones de objetos simples u otras escenas.
Este patrón lo podemos aplicar en las siguientes situaciones:
- Representar jerarquias de objetos simples y compuestos que puedan ser manejados de forma uniforme.
- Permitir a los clientes ignorar las diferencias entre objetos individuales o composiciones de objetos.
Los elementos implicados en este patrón son:
- Component: Declara la interfaz para objectos de la composición, implementa el comportamiento por defecto que será común a todas las clases, declara la instancia para el acceso y manejo de los componentes hijos y, opcionalmente, define la interfaz para acceder a los componentes padres en una estructura recursiva e implementa esta última si es necesario.
- Leaf: Representa los objetos simples de la composición. Estos objetos no tiene más hojas. Además, define el comportameinto de estos objetos primitivos.
- Composite: Define el comportamiento para componentes que tienen hijos, almacena objetos simples de la composición e implementa el comportamiento relacionado con los hijos en el “Component”.
- Client: Manipula los objetos de la composición a través de los métodos del “Component”.
Como en todos los casos anteriores, la aplicación del patrón tiene sus fortalezas, pero también provoca algunos pequeñas pegas como que puede hacer el diseño sea demasiado general y, en ocasiones, que sea díficil controlas los elementos que se incluyen en las diferentes composiciones y pertenecen a ellas. Para paliar este problema, se pueden incluir controles en tiempo de ejecución para poder controlarlo.
Pero, para ver de verdad como funciona esto, vamos a realizar un pequeña implementación como siempre. El típico ejemplo, como ya he comentado antes, suele ser algún editor gráfico, pero para nuestro ejemeplo vamos a utilizar algo que está en la naturaleza y que posee está estructura de árbol que perseguimos implementar, las moléculas. Como objetos simples, tendremos los átomos, y como objetos compuestos las moléculas, que a su vez pueden estar compuestas por otras moléculas.
Component:
public abstract class Component { protected String nombre; protected int weight; public Component(String nombre, int weight) { ... } abstract public void add(Component c); abstract public void remove(Component c); abstract public int obtainWeight(); }
Leaf:
public class Atom extends Component { public Atom(String nombre, int weight) { super(nombre, weight); ... } public void add(Component c) { ... } public void remove(Component c) { ... } public int obtainWeight() { return weight; } }
Composite:
public class Molecule extends Component { private List<Component> list; public Molecule(String nombre) { this.nombre = nombre; ... } public void add(Component c) { list.add(c); } public void remove(Component c) { list.add(c); } public int obtainWeight() { int total = 0; for(Component c : list) { total += c.obtainWeight(); } return total; } }
Client:
public class Laboratory { public static void main(String[] args) { Component a1 = new Atom("a1", 12); Component a2 = new Atom("a2", 10); Component m1 = new Molecule("m1"); m1.add(a1); m1.add(a2); System.out.println(a1.obtainWeight()); System.out.println(a2.obtainWeight()); System.out.println(m1.obtainWeight()); } }
Con este código en el cliente, estaríamos creando dos nuevos átomos y con ellos formando una molécula y utilizando sus métodos para obtener su peso molecular o atómico segun corresponda.
Otros patrones relacionados con este son: el patrón Decorator que muchas veces se suele utilizar en conjunto con Composite teniendo de esta forma una clase común padre. El patrón Flyweight, que aún no hemos visto, pero permite compartir componentes, aunque se pierde la accesibilidad a los componentes padres. El Iterator, tampoco lo hemos visto aún, puede ser usado para cruzar objetos Composite. El patrón Visitor, tampoco lo hemos visto aún, localiza operaciones y componentes que podrían de otra forma distribuirse por las clases Composite y Leaf.
Nos vemos.