Menu
Coddy logo textTech

Patrón Composite

Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 75 de 87.

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. Permite a los clientes tratar objetos individuales y composiciones de objetos de manera uniforme a través de una interfaz común.

Piense en un sistema de archivos: una carpeta puede contener archivos y otras carpetas, que a su vez pueden contener más archivos y carpetas. Ya sea que esté tratando con un solo archivo o con una estructura de carpetas completa, querrá realizar operaciones como "get size" de la misma manera. El patrón define una interfaz Component, objetos Leaf (elementos individuales) y objetos Composite (contenedores que albergan hijos):

interface FileComponent {
    void display(String indent);
    int getSize();
}

class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

El composite delega operaciones a sus hijos, permitiendo estructuras recursivas. Los clientes interactúan con el árbol sin saber si están trabajando con una hoja o un composite:

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

El patrón Composite es ideal para representar jerarquías como organigramas, componentes de UI o sistemas de menús donde es necesario tratar a los grupos y a los individuos de manera idéntica.

challenge icon

Desafío

Fácil

¡Vamos a construir un sistema de organigrama utilizando el Composite Pattern! Crearás una estructura donde los departamentos pueden contener empleados y otros subdepartamentos, lo que te permitirá calcular los salarios totales y mostrar la jerarquía de manera uniforme, ya sea que estés mirando a un solo empleado o a una división completa.

Organizarás tu código en cuatro archivos:

  • OrganizationComponent.java: Define la interfaz del componente que implementarán tanto los empleados como los departamentos. Debe declarar dos métodos: showDetails(String indent) para mostrar el componente con la sangría adecuada, y getSalary() que devuelve el salario total como un entero.
  • Employee.java: Crea la clase hoja (leaf) que representa a los trabajadores individuales. Un Employee tiene un nombre (String) y un salario (int), ambos establecidos a través del constructor. Cuando se llama a showDetails, debe imprimir [indent][name]: $[salary]. El método getSalary simplemente devuelve el salario del empleado.
  • Department.java: Crea la clase compuesta (composite) que puede contener tanto empleados como subdepartamentos. Un Department tiene un nombre (String) y mantiene una lista de hijos de tipo OrganizationComponent. Debe tener un método add(OrganizationComponent component) para añadir miembros. Cuando se llama a showDetails, imprime [indent][name] Department, luego llama a showDetails en cada hijo con una sangría mayor (añade dos espacios). El método getSalary devuelve la suma de los salarios de todos los hijos.
  • Main.java: ¡Construye tu organización! Recibirás cuatro entradas: dos nombres de empleados con sus salarios, y un nombre de subdepartamento con un empleado.

    Crea un departamento "Engineering" como raíz. Añade dos empleados utilizando los dos primeros pares de nombre/salario. Luego crea un subdepartamento usando la tercera entrada, añade un empleado usando el cuarto par de nombre/salario, y añade este subdepartamento a Engineering.

    Llama a showDetails("") en el departamento Engineering, luego imprime el salario total en el formato Total Salary: $[amount].

Recibirás las entradas en este orden: nombre del empleado1 (String), salario del empleado1 (int), nombre del empleado2 (String), salario del empleado2 (int), nombre del subdepartamento (String), nombre del empleado3 (String), salario del empleado3 (int).

Por ejemplo, con las entradas Alice, 75000, Bob, 65000, QA, Charlie, 55000, tu salida sería:

Engineering Department
  Alice: $75000
  Bob: $65000
  QA Department
    Charlie: $55000
Total Salary: $195000

Observa cómo los mismos métodos showDetails y getSalary funcionan a la perfección ya sea que se llamen para un solo empleado o para un departamento completo que contiene subdepartamentos anidados. ¡La estructura compuesta maneja la recursividad automáticamente, permitiéndote tratar toda la jerarquía de manera uniforme!

Hoja de referencia

El Patrón Composite es un patrón de diseño estructural que compone objetos en estructuras de árbol para representar jerarquías de parte-todo. Permite tratar objetos individuales y composiciones de manera uniforme a través de una interfaz común.

El patrón consta de tres elementos clave:

  • Componente: Interfaz que define las operaciones comunes
  • Hoja: Objetos individuales que implementan la interfaz del componente
  • Composite: Objetos contenedores que albergan hijos y les delegan operaciones

Ejemplo de implementación de un sistema de archivos:

// Interfaz Componente
interface FileComponent {
    void display(String indent);
    int getSize();
}

// Clase Hoja
class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

// Clase Composite
class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

Uso de la estructura composite:

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

El composite delega las operaciones a sus hijos de forma recursiva. Los clientes interactúan con el árbol sin saber si están trabajando con una hoja o con un composite.

Casos de uso: Organigramas, componentes de interfaz de usuario, sistemas de menús, sistemas de archivos; cualquier jerarquía donde los grupos y los individuos necesiten un tratamiento idéntico.

Pruébalo tú mismo

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Leer entradas
        String emp1Name = scanner.nextLine();
        int emp1Salary = Integer.parseInt(scanner.nextLine());
        String emp2Name = scanner.nextLine();
        int emp2Salary = Integer.parseInt(scanner.nextLine());
        String subDeptName = scanner.nextLine();
        String emp3Name = scanner.nextLine();
        int emp3Salary = Integer.parseInt(scanner.nextLine());
        
        // TODO: Crear el departamento "Engineering" como la raíz
        
        // TODO: Crear y añadir dos empleados a Engineering usando los datos de emp1 y emp2
        
        // TODO: Crear un subdepartamento usando subDeptName
        
        // TODO: Crear y añadir un empleado al subdepartamento usando los datos de emp3
        
        // TODO: Añadir el subdepartamento a Engineering
        
        // TODO: Llamar a showDetails("") en el departamento Engineering
        
        // TODO: Imprimir el salario total en el formato: Total Salary: $[amount]
    }
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos