El patrón Composite es un patrón de diseño estructural que permite componer objetos en estructuras de árbol para representar jerarquías de parte-todo.
El objetivo es permite que los clientes traten de manera uniforme a los objetos individuales y a los complejos.
Es especialmente útil para modelar y trabajar con estructuras complejas.
Construir objetos complejos compuestos de partes más simples, y cómo tratar de manera uniforme tanto a las partes individuales como al conjunto.
La solución que propone el Composite es:


Este patrón es recomendable cuando queremos:
Simplicidad para los Clientes: Facilita a los clientes el trabajo con estructuras complejas al tratar los objetos simples y compuestos de igual manera.
Flexibilidad en la Estructura: Permite construir estructuras complejas y jerárquicas de objetos.
Fácil Agregado de Nuevos Tipos de Componentes: Permite agregar nuevos tipos de componentes sin cambiar el código existente.
Dificultad para Restringir Tipos de Componentes: Puede ser difícil restringir los tipos de componentes que se pueden agregar a objetos compuestos.
Rendimiento en Estructuras Muy Grandes: El rendimiento puede ser un problema si la estructura del árbol es muy grande y profunda.
Debemos desarrollar un módulo de gestión de archivos para un sistema que debe organizar y manipular una gran cantidad de documentos y carpetas.
El sistema debe permitir a los usuarios realizar diversas operaciones como ver, agregar o eliminar archivos y carpetas.
El desafío principal esta tratar archivos y carpetas de manera uniforme. Los archivos son elementos individuales, mientras que las carpetas pueden contener múltiples archivos y otras carpetas.
Esta estructura jerárquica de parte-todo complica la interacción del usuario con el sistema, ya que las operaciones sobre archivos y carpetas podrían requerir implementaciones muy diferentes. Implementar cada operación para cada tipo de elemento (archivo o carpeta) termina en un código repetitivo y difícil de mantener, sobre todo cuando se agregan nuevas operaciones o tipos de elementos.
Vamos a usar el patrón Composite que nos permite tratar tanto archivos individuales (elementos ‘Leaf’) como carpetas (elementos ‘Composite’) de manera uniforme.
Al usar una interfaz común para ambos, simplificamos la lógica del cliente en la interacción con archivos y carpetas. Así, las carpetas pueden contener y gestionar varios elementos (ya sean archivos u otras carpetas) de la misma manera que un archivo individual.
Definimos una interfaz FileSystemComponent, que define operaciones comunes, como printName, add y remove.
La clase File representa archivos individuales y maneja operaciones básicas. No permite agregar ni eliminación componentes, ya que es un elemento Hoja.
La clase Directory representa una carpeta, que puede contener tanto archivos como otras carpetas. Implementa métodos para agregar y eliminar componentes.
En el cliente creamos instancias de File y Directory, y las organizamos en una estructura de árbol. Luego invocamos a printName en el directorio principal para imprimir los nombres de todos los archivos y subdirectorios.

Codificamos en Java lo que preparamos en el diagrama.
Definimos la Interfaz de FileSystemComponent (Component):
interface FileSystemComponent {
void printName();
void add(FileSystemComponent component) throws Exception;
void remove(FileSystemComponent component) throws Exception;
}
Implementamos las clase File (Hoja):
class File implements FileSystemComponent {
private String name;
public File(String name) {
this.name = name;
}
public void printName() {
System.out.println("Archivo: " + name);
}
public void add(FileSystemComponent component) {
throw new UnsupportedOperationException();
}
public void remove(FileSystemComponent component) {
throw new UnsupportedOperationException();
}
}
Implementamos las clase Directory (Composite):
class Directory implements FileSystemComponent {
private String name;
private List children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void printName() {
System.out.println("Carpeta: " + name);
for (FileSystemComponent component : children) {
component.printName();
}
}
public void add(FileSystemComponent component) {
children.add(component);
}
public void remove(FileSystemComponent component) {
children.remove(component);
}
}
El código cliente:
public class Client {
public static void main(String[] args) {
FileSystemComponent file1 = new File("file1.txt");
FileSystemComponent file2 = new File("file2.txt");
FileSystemComponent dir1 = new Directory("dir1");
try {
dir1.add(file1);
dir1.add(file2);
FileSystemComponent file3 = new File("file3.txt");
FileSystemComponent mainDir = new Directory("main");
mainDir.add(dir1);
mainDir.add(file3);
mainDir.printName();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Los participantes que vimos antes son: Component, Leaf, Composite, Client:
Al usar el patrón Composite, nuestro sistema de gestión de archivos se vuelve más sencillo y escalable. Permite fácilmente agregar nuevas operaciones o tipos de elementos sin alterar la lógica existente. Esto no solo mejora la mantenibilidad del código, sino que también ofrece una experiencia de usuario más coherente y eficiente al tratar con la estructura jerárquica de archivos y carpetas.
A menudo se usa con Composite para añadir responsabilidades a los componentes individuales.
Puede ser combinado con Composite para compartir componentes.