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()); // 650El 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.
Desafío
FácilVamos 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 interfazIOrgComponenten el espacio de nombresOrganizationcon dos miembros: una propiedadName(string) y un métodoGetSalary()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 elementosIOrgComponent. Incluye un métodoAdd(IOrgComponent component)para añadir miembros. Su métodoGetSalary()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 (
employeeodepartment), seguido del nombre, y para los empleados, su salario. Para los departamentos, también recibirás el nombre del departamento padre (orootsi 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
QALa salida debería ser:
Engineering Total Salary: 13500El 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()); // 650El 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()}");
}
}
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
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes