Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Patrones de Diseño Estructurales

Composite

Componer objetos en estructuras de árbol

Universidad Nacional de Rio Negro - Sede Andina

Definición

El patrón Composite permite componer objetos en estructuras de árbol para representar jerarquías parte-todo, permitiendo que los clientes traten objetos individuales y composiciones de objetos de manera uniforme.

Origen e Historia

Gang of Four 1994. Surge de la necesidad de tratar uniformemente estructuras jerárquicas (árboles) sin que el cliente conozca si está trabajando con una hoja o un compuesto. Se popularizó en sistemas de archivos, widgets de UI, y procesadores de documentos.

Motivación

Necesario cuando:

Contexto

Escenario: Archivos/carpetas, widgets UI, menús anidados

Anatomía:

Cuando aplica

Usá Composite cuando:

Cuando no aplica

Evitá Composite cuando:

Consecuencias de su uso

Positivas

Negativas

Alternativas

PatrónPropósitoDiferencia
DecoratorAgregar responsabilidadesEnvuelve individual
FlyweightCompartir datosOptimiza memoria
IteratorRecorrer coleccionesPatrón de comportamiento

Estructura

Problema

// ❌ Sin Composite: Se debe diferenciar entre tipos
class Archivo {
    private String nombre;
    private int tamaño;
    
    public int obtenerTamaño() {
        return tamaño;
    }
}

class Carpeta {
    private String nombre;
    private List<Archivo> archivos;
    
    public int obtenerTamaño() {
        int total = 0;
        for (Archivo archivo : archivos) {
            total += archivo.obtenerTamaño();
        }
        return total;
    }
}

// ¿Y si quiero carpetas anidadas?
// ¿Y si quiero un método que funcione con ambos?

Solución

/**
 * Componente común: interfaz uniforme.
 */
public abstract class ElementoSistemaArchivos {
    protected String nombre;
    
    public ElementoSistemaArchivos(String nombre) {
        this.nombre = nombre;
    }
    
    abstract void mostrar(int indentacion);
    abstract int obtenerTamaño();
    
    public String getNombre() {
        return nombre;
    }
}

/**
 * Hoja: Archivo sin hijos.
 */
public class Archivo extends ElementoSistemaArchivos {
    private int tamaño;
    
    public Archivo(String nombre, int tamaño) {
        super(nombre);
        this.tamaño = tamaño;
    }
    
    @Override
    public void mostrar(int indentacion) {
        for (int i = 0; i < indentacion; i++) {
            System.out.print("  ");
        }
        System.out.println("📄 " + nombre + " (" + tamaño + " KB)");
    }
    
    @Override
    public int obtenerTamaño() {
        return tamaño;
    }
}

/**
 * Compuesto: Carpeta que puede contener archivos y carpetas.
 */
public class Carpeta extends ElementoSistemaArchivos {
    private List<ElementoSistemaArchivos> elementos;
    
    public Carpeta(String nombre) {
        super(nombre);
        this.elementos = new ArrayList<>();
    }
    
    public void agregar(ElementoSistemaArchivos elemento) {
        elementos.add(elemento);
    }
    
    public void remover(ElementoSistemaArchivos elemento) {
        elementos.remove(elemento);
    }
    
    @Override
    public void mostrar(int indentacion) {
        for (int i = 0; i < indentacion; i++) {
            System.out.print("  ");
        }
        System.out.println("📁 " + nombre);
        
        for (ElementoSistemaArchivos elemento : elementos) {
            elemento.mostrar(indentacion + 1);
        }
    }
    
    @Override
    public int obtenerTamaño() {
        int total = 0;
        for (ElementoSistemaArchivos elemento : elementos) {
            total += elemento.obtenerTamaño();
        }
        return total;
    }
}

// ✅ Uso uniforme
Carpeta raiz = new Carpeta("C:\\");

Carpeta documentos = new Carpeta("Documentos");
Archivo tesis = new Archivo("tesis.pdf", 5000);
Archivo carta = new Archivo("carta.doc", 100);

documentos.agregar(tesis);
documentos.agregar(carta);

raiz.agregar(documentos);
raiz.agregar(new Archivo("readme.txt", 10));

// Mismo método para todos
raiz.mostrar(0);
System.out.println("Tamaño total: " + raiz.obtenerTamaño() + " KB");

Diagramas

Diagrama de Clases

Diagrama de Secuencia

Ejemplos

Ejemplo 1: Estructura de Menú

public abstract class Elemento {
    protected String nombre;
    
    public Elemento(String nombre) {
        this.nombre = nombre;
    }
    
    abstract void mostrar();
}

public class OpcionMenu extends Elemento {
    @Override
    public void mostrar() {
        System.out.println("→ " + nombre);
    }
}

public class SubmenuMenu extends Elemento {
    private List<Elemento> items = new ArrayList<>();
    
    public SubmenuMenu(String nombre) {
        super(nombre);
    }
    
    public void agregar(Elemento item) {
        items.add(item);
    }
    
    @Override
    public void mostrar() {
        System.out.println("📋 " + nombre);
        for (Elemento item : items) {
            item.mostrar();
        }
    }
}

// Uso
SubmenuMenu menuPrincipal = new SubmenuMenu("Archivo");
menuPrincipal.agregar(new OpcionMenu("Abrir"));
menuPrincipal.agregar(new OpcionMenu("Guardar"));

SubmenuMenu menuRecientes = new SubmenuMenu("Abiertos Recientemente");
menuRecientes.agregar(new OpcionMenu("documento1.txt"));
menuRecientes.agregar(new OpcionMenu("documento2.txt"));

menuPrincipal.agregar(menuRecientes);
menuPrincipal.mostrar();

Ejercicios

Resumen

El patrón Composite es fundamental para trabajar con estructuras jerárquicas. Al permitir tratar uniformemente hojas y compuestos, simplifica enormemente el código cliente y facilita la adición de nuevos tipos. Es ampliamente usado en sistemas que requieren representar relaciones padre-hijo de forma flexible.

Próximo paso

Para seguir, conviene pasar a el material siguiente, donde el recorrido continúa sobre esta base.