El patrón Composite
Existen ocasiones en las que sabemos que un objeto va a estar compuesto de otros muchos, que un contenedor va a albergar distintos componentes, y que todos esos componentes van a tener un comportamiento o funcionalidad similar. En definitiva, sabemos que nuestra implementación va a tener una estructura jerárquica.
Este patrón define una serie de conceptos fundamentales para su comprensión:
- Component: implementa un comportamiento común entre las clases mediante una interfaz de manipulacion a sus nodos hijos. Es el nodo principal del que derivan los demás.
- Leaf: representa los nodos “hoja” (no poseen hijos). Define comportamientos para objetos primitivos.
- Composite: Son los componentes del patrón. Extienden el Componente principal y pueden ser padres de otros nodos hijos.
- Cliente: manipula objetos de la jerarquía a través del nodo principal.
Los clientes usan la interfaz de Component para interactuar con objetos en la estructura Composite. Si el nodo con el que interactúa es un nodo primitivo o «leaf» lo hace directamente, pero en caso de interactuar con un Composite, lo hace a través de la interfaz que provee el Component.
El diagrama UML del patrón es de esta forma:
Nota importante: Este diagrama difiere un poco del diagrama «oficial» para evitar el error, a mi juicio, de la implementacion de métodos inútiles como por ejemplo en la propia Wikipedia. Bajo la implementación tradicional, se requiere que las clases «leaf» lancen errores de tipo UnsupportedOperationException. Para mí no tiene mucho sentido cuando puedes crear un nuevo nivel de abstracción que además separe responsabilidades entre la clase que define la operacion (Component) y la que define los métodos jerárquicos (Composite).
Si nos damos cuenta, aquí los nodos «Leaf» no implementan los métodos requeridos para realizar la jerarquía, y solo ejecutan operaciones. Los Composite son los encargados de hacer que los Components (tanto Leafs como Composite) ejecuten una determinada acción.
A continuación expongo un ejemplo de código de una clase Composite y otra Component bajo el anterior diagrama. Vamos a crear un ejemplo con el mapa político de Europa. En nuestro caso, la interfaz común será Territorio, que podrá contener otros territorios, paises y provincias consecutivamente hasta llegar a los nodos hijos finales: las ciudades. La finalidad será contar el número de habitantes.
Este patrón permite la generación de una jerarquía fácilmente, asi como una interfaz de acceso a toda la jerarquía para el cliente, aislándolo de la implementación concreta y por tanto siguiendo con los principios de diseño.