Fundamentos del Patrón Composite
El patrón de diseño Composite (Compuesto) es un patrón estructural que permite componer objetos en estructuras de árbol para representar jerarquías de "parte-todo". Su principle ventaja radica en que permite a los clientes tratar de manera unfiorme tanto a los objetos individuales (hojas) como a las composiciones complejas de objetos (ramas).
Enfoques de Implementación: Transparencia vs. Seguridad
Al diseñar la interfaz o clase base (Componente), existen dos estrategias principales para manejar la gestión de elementos hijos:
- Enfoque Transparente: Se declaran los métodos de gestión de hijos (como
agregaryeliminar) directamente en la interfaz o clase base del Componente. Esto garantiza que las hojas y las ramas tengan exactamente la misma interfaz, simplificando el código del cliente. Sin embargo, las hojas se ven obligadas a heredar o implementar métodos que no tienen sentido para ellas. - Enfoque Seguro: Los métodos de gestión de hijos se declaran únicamente en la clase que representa las ramas (Composite). Las hojas no heredan estos métodos. Aunque esto es más seguro desde el punto de vista del diseño de tipos, pierde la transparencia, obligando al cliente a verificar el tipo de objeto antes de intentar modificar la estructura.
Escenarios de Aplicación
Este patrón es ideal cuando la estructura del dominio puede representarse como un árbol y se desea que el código cliente ignore las diferencias entre las composiciones y los elementos individuales, interactuando con todos ellos a través de una interfaz común.
Ejemplo Práctico: Sistema de Archivos
A continuación, se presenta una implementación utilizando el enfoque transparente para modelar un sistema de archivos compuesto por directorios y archivos. Se utilizan estructuras modernas de Java para optimizar la legibilidad y el rendimiento.
package com.designpatterns.composite;
/**
* Componente base que define la interfaz común para archivos y directorios.
*/
public abstract class NodoSistemaArchivos {
protected String nombre;
public NodoSistemaArchivos(String nombre) {
this.nombre = nombre;
}
public String getNombre() {
return nombre;
}
// Operaciones de gestión de hijos (Enfoque Transparente)
public void agregar(NodoSistemaArchivos nodo) {
throw new UnsupportedOperationException("No se pueden agregar elementos a este nodo.");
}
public void eliminar(NodoSistemaArchivos nodo) {
throw new UnsupportedOperationException("No se pueden eliminar elementos de este nodo.");
}
// Operaciones comunes
public abstract void mostrarEstructura(int nivel);
public abstract long obtenerTamanoTotal();
}
package com.designpatterns.composite;
import java.util.ArrayList;
import java.util.List;
/**
* Clase Composite que representa un directorio (rama del árbol).
*/
public class Directorio extends NodoSistemaArchivos {
private final List<nodosistemaarchivos> elementos = new ArrayList<>();
public Directorio(String nombre) {
super(nombre);
}
@Override
public void agregar(NodoSistemaArchivos nodo) {
elementos.add(nodo);
}
@Override
public void eliminar(NodoSistemaArchivos nodo) {
elementos.remove(nodo);
}
@Override
public void mostrarEstructura(int nivel) {
String indentacion = " ".repeat(nivel);
System.out.println(indentacion + "+ " + nombre + "/");
for (NodoSistemaArchivos elemento : elementos) {
elemento.mostrarEstructura(nivel + 1);
}
}
@Override
public long obtenerTamanoTotal() {
return elementos.stream()
.mapToLong(NodoSistemaArchivos::obtenerTamanoTotal)
.sum();
}
}
</nodosistemaarchivos>
package com.designpatterns.composite;
/**
* Clase Leaf que representa un archivo individual (hoja del árbol).
*/
public class Archivo extends NodoSistemaArchivos {
private final long tamanoEnBytes;
public Archivo(String nombre, long tamanoEnBytes) {
super(nombre);
this.tamanoEnBytes = tamanoEnBytes;
}
@Override
public void mostrarEstructura(int nivel) {
String indentacion = " ".repeat(nivel);
System.out.println(indentacion + "- " + nombre + " (" + tamanoEnBytes + " bytes)");
}
@Override
public long obtenerTamanoTotal() {
return tamanoEnBytes;
}
}
package com.designpatterns.composite;
/**
* Cliente que interactúa con la estructura compuesta de manera uniforme.
*/
public class AplicacionPrincipal {
public static void main(String[] args) {
// Crear la estructura de directorios
Directorio raiz = new Directorio("raiz");
Directorio documentos = new Directorio("documentos");
Directorio imagenes = new Directorio("imagenes");
// Crear archivos individuales
Archivo reporte = new Archivo("reporte_anual.pdf", 2048);
Archivo notas = new Archivo("notas.txt", 512);
Archivo foto = new Archivo("vacaciones.jpg", 4096);
Archivo logo = new Archivo("logo.png", 1024);
// Construir el árbol jerárquico
documentos.agregar(reporte);
documentos.agregar(notas);
imagenes.agregar(foto);
imagenes.agregar(logo);
raiz.agregar(documentos);
raiz.agregar(imagenes);
raiz.agregar(new Archivo("readme.md", 128));
// Ejecutar operaciones recursivas
System.out.println("=== Estructura del Sistema de Archivos ===");
raiz.mostrarEstructura(0);
System.out.println("\n=== Cálculo de Tamaños ===");
System.out.println("Tamaño total de 'raiz': " + raiz.obtenerTamanoTotal() + " bytes");
System.out.println("Tamaño total de 'documentos': " + documentos.obtenerTamanoTotal() + " bytes");
}
}