Saltar al contenidoJMRG
Todas las entradas

Patrón Composite: Estructuras Jerárquicas Uniformes

design-patternsstructuralcomposite

Intención

Composite permite tratar objetos individuales y composiciones de objetos de manera uniforme. Compone objetos en estructuras de árbol para representar jerarquías parte-todo.

El Problema

Sin Composite, el código cliente necesita distinguir entre objetos simples y compuestos:

// Código cliente complejo
function calculatePrice(item: unknown): number {
  if (item instanceof Product) {
    return item.price;
  } else if (item instanceof Box) {
    let total = 0;
    for (const child of item.children) {
      total += calculatePrice(child); // Recursión manual
    }
    return total;
  }
  throw new Error('Unknown item type');
}

La Solución: Composite

classDiagram
    class Component {
        <<interface>>
        +operation(): number
        +add(child: Component)
        +remove(child: Component)
    }

    class Leaf {
        +operation(): number
    }

    class Composite {
        -children: Component[]
        +operation(): number
        +add(child: Component)
        +remove(child: Component)
    }

    Component <|.. Leaf
    Component <|.. Composite
    Composite o--> Component

Implementación TypeScript

// Componente base
interface FileSystemNode {
  getName(): string;
  getSize(): number;
  print(indent?: string): void;
}

// Hoja: Archivo
class File implements FileSystemNode {
  constructor(
    private name: string,
    private size: number
  ) {}

  getName(): string {
    return this.name;
  }

  getSize(): number {
    return this.size;
  }

  print(indent = ''): void {
    console.log(`${indent}📄 ${this.name} (${this.size} bytes)`);
  }
}

// Composite: Directorio
class Directory implements FileSystemNode {
  private children: FileSystemNode[] = [];

  constructor(private name: string) {}

  getName(): string {
    return this.name;
  }

  getSize(): number {
    return this.children.reduce((sum, child) => sum + child.getSize(), 0);
  }

  add(node: FileSystemNode): this {
    this.children.push(node);
    return this;
  }

  print(indent = ''): void {
    console.log(`${indent}📁 ${this.name}/`);
    for (const child of this.children) {
      child.print(indent + '  ');
    }
  }
}

// Uso - El cliente trata todo uniformemente
const root = new Directory('project')
  .add(new File('package.json', 1024))
  .add(new File('README.md', 2048))
  .add(
    new Directory('src')
      .add(new File('index.ts', 512))
      .add(new File('utils.ts', 256))
  );

// Operación uniforme en toda la jerarquía
console.log(`Total: ${root.getSize()} bytes`);
root.print();

Cuándo Usar

Situación Recomendación
Estructuras de árbol (DOM, FS) ✅ Recomendado
Menús con submenús ✅ Recomendado
Expresiones aritméticas ✅ Recomendado
Estructuras planas sin jerarquía ❌ Innecesario

Conclusión

Composite simplifica el código cliente al permitir tratar elementos individuales y grupos de la misma manera.