Mi granito de java: Composite

miércoles, 1 de junio de 2011

Composite

El patrón Composite sirve para construir algoritmos u 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. Dicho de otra forma, permite construir objetos complejos componiendo de forma recursiva objetos similares en una estructura 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.

Este patrón busca representar una jerarquía de objetos conocida como “parte-todo”, donde se sigue la teoría de que las "partes" forman el "todo", siempre teniendo en cuenta que cada "parte" puede tener otras "parte" dentro.

Se debe utilizar este patrón cuando:
  • Se busca representar una jerarquía de objetos como “parte-todo”.
  • Se busca que el cliente puede ignorar la diferencia entre objetos primitivos y compuestos (para que pueda tratarlos de la misma manera).

Diagrama UML


Component: implementa un comportamiento común entre las clases y declara una interface de manipulación a los padres en la estructura recursiva.
Leaf: representa los objetos “hoja” (no poseen hijos). Define comportamientos para objetos primitivos.
Composite: define un comportamiento para objetos con hijos. Almacena componentes hijos. implementa operaciones de relación con los hijos.
Cliente: manipula objetos de la composición a través de Component.
Los clientes usan la interfaz de Component para interactuar con objetos en la estructura Composite. Si el receptor es una hoja, la interacción es directa. Si es un Composite, se debe llegar a los objetos “hijos”, y puede llevar a utilizar operaciones adicionales.

Ejemplo

Vamos a realizar un ejemplo de un Banco. Un banco puede tener muchos sectores: Gerencia, Administrativo, RRHH, Cajas, etc. Cada uno de estos sectores tendrá empleados que cobran un sueldo. En nuestro caso utilizaremos el Composite para calcular la sumatoria de sueldos de la empresa. Para ello definimos la Interface y el Composite.

En la clase Composite está todo el secreto del patrón: contiene una colección de hijos del tipo ISueldo que los va agregando con el método agrega(ISueldo) y cuando al composite le ejecutan el getSueldo() simplemente recorre sus hijos y les ejecutas el método. Sus hijos podrán ser de 2 tipos: Composite (que a su vez harán el mismo recorrido con sus propios hijos) u Hojas que devolverán un valor.
En nuestro ejemplo hay varios sectores, solo pongo uno para no llenar de pics el ejemplo. Pero todos son muy parecidos: la única relación que tienen con el composite es que heredan de él.
Ahora veamos el caso del empleado que trabaja en el banco (sería una hoja):

Listo, ahora veamos la forma de concatenar todo. Yo lo hice en el Main (que vendría a representar al cliente), pero en realidad el armado del banco no siempre lo debe hacer el cliente. De hecho, podríamos utilizar un patrón creacional para que nos ayude en el armado.

El banco se compone de varios sectores, los cuales pueden tener personas o más sectores adentro.


Consecuencias.
  • Define jerarquías entre las clases.
  • Simplifica la interacción de los clientes.
  • Hace más fácil la inserción de nuevos hijos.
  • Hace el diseño más general.
  • Si la operación es compleja, puede ensuciar mucho el código y hacerlo ilegible.
Temas a tener en cuenta

Hay muchas formas de llevar a cabo este patrón. Muchas implementaciones implican referencias explicitas al padre para simplificar la gestión de la estructura.
Si el orden de los hijos provoca problemas utilizar Iterator.Para borrar elementos les recomiendo que un objeto elimine a sus hijos.
La mejor estructura para almacenar elementos depende de la eficiencia: si se atraviesa la estructura con frecuencia se puede dejar información en los hijos.

6 comentarios:

Anónimo dijo...

Hola Max, he estado siguiendo atentamente tu blog, te agradezco el trabajo!! Una consulta, respecto al ejemplo: ¿por que no elegir una clase Component que tenga de manera abstracta los métodos add(Component) y remove(Component) y que Hoja los implemente abriendo y cerrando llaves {} osea vacios, y Composite si los implemente? ¿No seria mas fiel al patrón? Ademas así el Cliente usa la interface de Component tal como muestra el diagrama UML y deja que el polimorfismo haga el resto.

Max dijo...

Hola, gracias por leer el blog! La interface Component es ISueldo. Esta interface la deben implementar las hojas y los composite. Si bien ISueldo deberia tener todos los metodos que vos decis, en este caso agregaria un poco mas de confusion porque las hojas no deberian agregar y se que no hay un nivel mas abajo que eso. Ahora bien, si no estoy seguro si habra un nivel debajo de mis hojas actuales o bien, los composite no pueden utilizar la herencia ya que tienen otras clases padres, no me queda otra opcion que colocar esos metodos en la interface.

Anónimo dijo...

Tu ejemplo esta incompleto ....

Anónimo dijo...

ME HICISTE PERDER MI TIEMPO.............BYE

Damian dijo...

Todas las clases "Sector*" que faltan son iguales a "SectorCajas" y la clase "Composite" es "Banco", solo tienes que renombrarla.

Saludos

Anónimo dijo...

uhhh con razon, ya decia como hiciste la clase banco XD gracias por aclarar que la clase "Composite" es "Banco" :D