Las es­tru­c­tu­ras de datos dinámicas requieren una es­cru­c­tu­ra je­rá­r­qui­ca clara y concisa. A menudo, llevar tales es­tru­c­tu­ras a la práctica no resulta nada fácil. No es nada eficiente, por ejemplo, consultar cada vez el tipo de un objeto antes de que los datos se hayan procesado. En pa­r­ti­cu­lar, cuando muchos objetos simples se en­cue­n­tran con objetos co­m­pue­s­tos, se re­co­mie­n­da usar el patrón de diseño Composite. Al ocu­l­tar­les sus di­fe­re­n­cias, este paradigma de de­sa­rro­llo de software facilita a los clientes actuar del mismo modo ante los objetos simples y co­m­pue­s­tos.

¿Qué es el patrón Composite?

El patrón de diseño Composite (Composite Pattern) es uno de los 23 patrones de diseño “GoF” para el de­sa­rro­llo de software que fueron pu­bli­ca­dos en 1994 por Erich Gamma, Richard Helm, Ralph Johnson, y John Vlissides, los llamados “Gang of Four” o la banda de los cuatro. Así como el patrón Facade y el patrón Decorator, se trata de un patrón de diseño que agrupa objetos complejos y clases en es­tru­c­tu­ras mayores.

El concepto básico del patrón Composite consiste en re­pre­se­n­tar objetos simples y sus co­n­tai­ne­rs (o co­n­te­ne­do­res, también llamados co­le­c­cio­nes en algunos lenguajes, o sea: grupos de objetos) en una clase abstracta de manera que puedan ser tratados uni­fo­r­me­me­n­te. Este tipo de es­tru­c­tu­ra se conoce como jerarquía parte-todo (en inglés: part-whole hierarchy), en la que un objeto es siempre, o una parte de un todo, o un todo compuesto por varias partes.

¿Qué problemas resuelve un diagrama UML de patrón Composite?

La finalidad del patrón Composite es, como en todos los patrones “GoF”, mejorar la gestión de problemas de diseño re­cu­rre­n­tes en la pro­gra­ma­ción orientada a objetos. El resultado deseado es un software flexible, ca­ra­c­te­ri­za­do por objetos fá­ci­l­me­n­te im­ple­me­n­ta­bles, in­te­r­ca­m­bia­bles, re­uti­li­za­bles y te­s­tea­bles. A tal efecto, el patrón de diseño Composite describe un método según el cual los objetos simples y complejos pueden ser tratados de la misma manera. De este modo, se puede crear una es­tru­c­tu­ra de objetos fá­ci­l­me­n­te in­te­li­gi­ble que permite al cliente un acceso altamente eficiente. Además, también se minimiza la pro­ba­bi­li­dad de error en el código.

Patrón de diseño Composite: re­pre­se­n­ta­ción gráfica (UML)

Para im­ple­me­n­tar je­ra­r­quías parte-todo, el patrón Composite pro­po­r­cio­na una interfaz de co­m­po­ne­n­tes unificada para los objetos simples, también llamados objetos leaf (hoja, en inglés), y los objetos complejos. Los objetos leaf simples integran esta interfaz di­re­c­ta­me­n­te, mientras que los objetos complejos envían pe­ti­cio­nes es­pe­cí­fi­cas del cliente au­to­má­ti­ca­me­n­te a la interfaz y a sus co­m­po­ne­n­tes su­bo­r­di­na­dos. Para el cliente, resulta to­ta­l­me­n­te in­di­fe­re­n­te de qué tipo de objeto se trate (parte o todo) ya que solo se comunica con la interfaz.

El siguiente diagrama de clases en formato UML muestra más cla­ra­me­n­te las co­ne­xio­nes y je­ra­r­quías de un software basado en patrón Composite.

Pros y contras del patrón de diseño Composite

El patrón Composite es una constante en el de­sa­rro­llo de software. Los proyectos con es­tru­c­tu­ras altamente anidadas tienden a be­ne­fi­ciar­se de la práctica me­to­do­lo­gía de los objetos: ya sean objetos simples o complejos, con de­pe­n­de­n­cias simples o complejas. El alcance del ani­da­mie­n­to es irre­le­va­n­te en el patrón Composite. Las di­fe­re­n­cias entre los tipos de objetos pueden ser ignoradas por el cliente de manera que las consultas no requieren funciones es­pe­cia­les, lo que conlleva la ventaja de que el código del cliente resulta simple y conciso.

Otra ventaja del patrón Composite es la fle­xi­bi­li­dad y sencilla es­ca­la­bi­li­dad que el patrón otorga al software. La interfaz con co­m­po­ne­n­tes uni­ve­r­sa­les permite la in­te­gra­ción de objetos simples y complejos sin mo­di­fi­ca­cio­nes en el código –ya sea en el cliente o en es­tru­c­tu­ras de objetos ya exi­s­te­n­tes.

Pero, a pesar de todas la ventajas, el patrón Composite y su interfaz unificada tienen algunos in­co­n­ve­nie­n­tes. La interfaz puede co­n­ve­r­ti­r­se en un dolor de cabeza para los de­sa­rro­lla­do­res, ya que la im­ple­me­n­ta­ción requiere algunas co­n­si­de­ra­cio­nes nada de­s­de­ña­bles. Por ejemplo, se debe decidir de antemano qué ope­ra­cio­nes van a ser definidas en la interfaz y cuáles en las clases Composite. Ajustes po­s­te­rio­res de las pro­pie­da­des Composite (por ejemplo, re­s­tri­n­gir qué tipo de elementos child se permiten) resultan ge­ne­ra­l­me­n­te co­m­pli­ca­dos y difíciles de im­ple­me­n­tar.

Ventajas De­s­ve­n­ta­jas
Facilita la re­pre­se­n­ta­ción de es­tru­c­tu­ras altamente anidadas Im­ple­me­n­ta­ción de interfaz de co­m­po­ne­n­tes co­m­pli­ca­da
Código simple y conciso Ajustes po­s­te­rio­res de las pro­pie­da­des Composite co­m­pli­ca­dos
Gran es­ca­la­bi­li­dad

Campos de apli­ca­ción del patrón Composite

Utilizar el patrón Composite compensa cuando existe la necesidad de realizar ope­ra­cio­nes en es­tru­c­tu­ras de datos dinámicas con je­ra­r­quías complejas (en términos de pro­fu­n­di­dad y anchura). Esto también se conoce como es­cru­c­tu­ra de árbol binaria, que resulta es­pe­cia­l­me­n­te in­te­re­sa­n­te para una amplia variedad de programas. Algunos ejemplos típicos:

Sistemas de datos: los sistemas de datos figuran entre los co­m­po­ne­n­tes más im­po­r­ta­n­tes de un software de di­s­po­si­ti­vo. Estos se pueden re­pre­se­n­tar pe­r­fe­c­ta­me­n­te usando el patrón Composite: los ficheros simples se re­pre­se­n­tan como objetos leaf y las carpetas (que pueden contener asimismo ficheros u otras carpetas) como objetos complejos.

Menús de software: los menús de programa re­pre­se­n­tan una apli­ca­ción típica de una es­tru­c­tu­ra de árbol binaria de acuerdo al patrón de diseño Composite. La barra de menú contiene una o más entradas pri­n­ci­pa­les (objetos complejos) como, por ejemplo, “Archivo”. Estas entradas pri­n­ci­pa­les pro­po­r­cio­nan acceso a varios objetos del menú que pueden se­le­c­cio­nar­se di­re­c­ta­me­n­te (objetos leaf) o que contienen otros submenús (objetos complejos).

In­te­r­fa­ces gráficas de usuario (GUI): las es­tru­c­tu­ras de árbol y el patrón Composite también juegan un papel im­po­r­ta­n­te en el diseño de in­te­r­fa­ces gráficas de usuario. Aparte de simples objetos leaf como botones, campos de texto o casillas de selección, los co­n­tai­ne­rs, como paneles o marcos, permiten una es­tru­c­tu­ra limpia y una mayor claridad.

Ejemplo de código: patrón Composite

El patrón Composite está más am­plia­me­n­te afianzado en el lenguaje de pro­gra­ma­ción Java. El patrón, entre otras cosas, es la base del AWT (Abstract Window Toolkit). El AWT es una práctica y extendida API con unas 50 clases de Java listas para utilizar para el de­sa­rro­llo de in­te­r­fa­ces Java mu­l­ti­pla­ta­fo­r­ma. En el siguiente código de ejemplo, extraído del artículo Patrón de diseño Composite en Java en baeldung.com, nos centramos en este popular lenguaje de pro­gra­ma­ción.

En nuestro ejemplo, se muestra una es­tru­c­tu­ra je­rá­r­qui­ca de de­pa­r­ta­me­n­tos en una empresa. Como primer paso, se define la interfaz del objeto De­pa­r­t­me­nt:

public interface Department {
	void printDepartmentName();
}

A co­n­ti­nua­ción, se definen dos simples clases leaf: Fi­na­n­cia­l­De­pa­r­t­me­nt (para el de­pa­r­ta­me­n­to fiscal) y Sa­le­s­De­pa­r­t­me­nt (para el de­pa­r­ta­me­n­to de ventas). Ambas im­ple­me­n­tan el método pri­n­t­De­pa­r­t­me­n­t­Na­me() de la interfaz del objeto, pero no contienen otros objetos De­pa­r­t­me­nt.

public class FinancialDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}
public class SalesDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}

Una clase composite que encaja con la jerarquía se define como “Hea­d­De­pa­r­t­me­nt”. Esta consiste en varios objetos De­pa­r­t­me­nt y, además del método pri­n­t­De­pa­r­t­me­n­t­Na­me(), contiene también otros métodos adi­cio­na­les para añadir objetos (ad­d­De­pa­r­t­me­nt) o eliminar objetos ya exi­s­te­n­tes (remo­ve­De­pa­r­t­me­nt):

public class HeadDepartment implements Department {
	private Integer id;
	private String name;
	private List<department> childDepartments;</department>
	public HeadDepartment(Integer id, String name) {
		this.id = id;
		this.name = name;
		this.childDepartments = new ArrayList<>();
	}
	public void printDepartmentName() {
		childDepartments.forEach(Department::printDepartmentName);
	}
	public void addDepartment(Department department) {
		childDepartments.add(department);
	}
	public void removeDepartment(Department department) {
		childDepartments.remove(department);
	}
}
Ir al menú principal