Menu
Coddy logo textTech

Patrón Composite

Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 61 de 70.

El patrón Composite es un patrón estructural que te permite componer objetos en estructuras de árbol y tratar objetos individuales y grupos de objetos de manera uniforme. Es perfecto para representar jerarquías de parte-todo donde quieres trabajar tanto con elementos individuales como con colecciones a través de la misma interfaz.

El patrón tiene tres componentes clave: una interfaz de componente que define operaciones comunes, clases hoja que representan objetos individuales y clases compuestas que contienen hijos e implementan la misma interfaz:

public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

La belleza de este patrón es que los clientes no necesitan saber si están trabajando con un solo archivo o con una jerarquía de carpetas completa:

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

El patrón Composite es ideal para sistemas de archivos, árboles de componentes de interfaz de usuario, organigramas o cualquier estructura donde los contenedores y el contenido deban tratarse de la misma manera. Simplifica el código cliente al eliminar la necesidad de distinguir entre elementos simples y complejos.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de organigrama utilizando el patrón Composite. Crearás una estructura donde tanto los empleados individuales como los departamentos (que contienen empleados u otros departamentos) puedan ser tratados de manera uniforme, permitiéndote calcular el costo total de los salarios en cualquier nivel de la jerarquía.

Organizarás tu código en tres archivos:

  • OrgComponent.cs: Define una interfaz IOrgComponent en el espacio de nombres Organization con dos miembros: una propiedad Name (string) y un método GetSalary() que devuelve un entero. Esta interfaz representa el contrato común que seguirán tanto los empleados individuales como los departamentos.
  • OrgClasses.cs: Crea dos clases en el mismo espacio de nombres:
    • Employee - una clase hoja que representa a un trabajador individual. Recibe un nombre y un salario en su constructor e implementa la interfaz devolviendo su propio salario.
    • Department - una clase compuesta que representa un grupo. Recibe un nombre en su constructor y mantiene una lista de elementos IOrgComponent. Incluye un método Add(IOrgComponent component) para añadir miembros. Su método GetSalary() debe devolver la suma de los salarios de todos sus miembros.
  • Program.cs: Construye una estructura organizacional basada en la entrada y calcula el salario total. Crearás departamentos y empleados, los anidarás adecuadamente y demostrarás cómo el patrón composite te permite tratar toda la jerarquía de manera uniforme.

Recibirás las siguientes entradas:

  • El número de componentes a crear
  • Para cada componente: el tipo (employee o department), seguido del nombre, y para los empleados, su salario. Para los departamentos, también recibirás el nombre del departamento padre (o root si es un departamento de nivel superior)

Formato de entrada para cada componente:

employee
{name}
{salary}
{parent_department}

department
{name}
{parent_department}

Después de construir la estructura, imprime el salario total del departamento raíz con el formato {root_name} Total Salary: {amount}.

Por ejemplo, si las entradas son:

5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QA

La salida debería ser:

Engineering Total Salary: 13500

El departamento Engineering contiene a Alice (5000), Bob (4500) y al departamento QA. QA contiene a Charlie (4000). Cuando llamas a GetSalary() en Engineering, este calcula recursivamente el total de todos los componentes anidados, ¡demostrando cómo el patrón Composite te permite trabajar con estructuras de árbol complejas a través de una interfaz simple y uniforme!

Hoja de referencia

El patrón Composite es un patrón estructural que compone objetos en estructuras de árbol y trata a los objetos individuales y a los grupos de manera uniforme a través de la misma interfaz. Es ideal para jerarquías de parte-todo.

El patrón tiene tres componentes clave:

  • Interfaz de componente: Define operaciones comunes
  • Clases hoja: Representan objetos individuales
  • Clases compuestas: Contienen hijos e implementan la misma interfaz

Ejemplo de implementación básica:

// Interfaz de componente
public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

// Clase hoja
public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

// Clase compuesta
public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

Uso: los clientes trabajan con elementos individuales y colecciones de manera uniforme:

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

El patrón Composite es útil para sistemas de archivos, árboles de componentes de interfaz de usuario, organigramas o cualquier estructura donde los contenedores y el contenido deban tratarse de la misma manera.

Pruébalo tú mismo

using System;
using System.Collections.Generic;
using Organization;

class Program
{
    public static void Main(string[] args)
    {
        int n = Convert.ToInt32(Console.ReadLine());
        
        // TODO: Crear un diccionario para almacenar departamentos por nombre
        // Esto te ayudará a encontrar los departamentos padre al añadir componentes
        Dictionary<string, Department> departments = new Dictionary<string, Department>();
        
        // TODO: Mantener el seguimiento del departamento raíz (root)
        Department root = null;
        
        for (int i = 0; i < n; i++)
        {
            string type = Console.ReadLine();
            
            if (type == "department")
            {
                string name = Console.ReadLine();
                string parent = Console.ReadLine();
                
                // TODO: Crear el departamento
                // TODO: Añadirlo al diccionario de departamentos
                // TODO: Si parent es "root", este es el departamento raíz
                // TODO: De lo contrario, encontrar el departamento padre y añadirle este departamento
            }
            else if (type == "employee")
            {
                string name = Console.ReadLine();
                int salary = Convert.ToInt32(Console.ReadLine());
                string parent = Console.ReadLine();
                
                // TODO: Crear el empleado
                // TODO: Encontrar el departamento padre y añadirle el empleado
            }
        }
        
        // TODO: Imprimir el salario total del departamento raíz
        // Formato: "{root_name} Total Salary: {amount}"
        Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
    }
}
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