Menu
Coddy logo textTech

Patrón Composite

Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 99 de 110.

El patrón Composite permite tratar objetos individuales y grupos de objetos de manera uniforme. Compone objetos en estructuras de árbol donde tanto los elementos individuales como las colecciones de elementos comparten la misma interfaz. Esto es perfecto para representar jerarquías como sistemas de archivos, organigramas o componentes de interfaz de usuario (UI).

El patrón tiene dos tipos de participantes: nodos Leaf (objetos individuales sin hijos) y nodos Composite (contenedores que contienen otros componentes). Ambos implementan una interfaz Component común:

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

La clase Folder puede contener tanto objetos File como otros objetos Folder. Cuando llamas a getSize() en una carpeta, esta calcula recursivamente el tamaño total de todo su contenido. El código cliente no necesita saber si está trabajando con un solo archivo o con una jerarquía de carpetas completa; ambos responden a los mismos métodos.

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 y otros departamentos) puedan ser tratados de manera uniforme, lo cual es perfecto para representar jerarquías de empresas.

Organizarás tu código en dos archivos:

  • organization.dart: Este archivo contiene tu interfaz de componente y las clases leaf (hoja) y composite (compuesta). Crea una clase abstracta OrganizationUnit con un getter name, un método getSalaryBudget() que devuelva un int, y un método display([String indent = '']) para mostrar la jerarquía. Construye una clase Employee (la hoja) que implemente OrganizationUnit. Debe tener un nombre y un salario, donde getSalaryBudget() devuelva el salario del empleado. Su método display() debe imprimir [indent]- [name] ($[salary]). Luego, crea una clase Department (el compuesto) que también implemente OrganizationUnit. Debe tener un nombre y mantener una lista de hijos de tipo OrganizationUnit. Añade un método add() para incluir empleados o subdepartamentos. Su getSalaryBudget() debe sumar recursivamente los presupuestos de todos los hijos. Su display() debe imprimir [indent]+ [name]/ seguido de la visualización de cada hijo con una indentación aumentada (añade dos espacios).
  • main.dart: Importa tu archivo de organización y construye una estructura de empresa. Crea un departamento 'Engineering' que contenga dos empleados: 'Alice' con un salario de 80000 y 'Bob' con un salario de 75000. Crea un departamento 'QA' con una empleada: 'Carol' con un salario de 65000. Ahora crea un departamento 'Technology' y añade tanto 'Engineering' como 'QA' como subdepartamentos. Llama a display() en el departamento Technology, luego imprime 'Total Budget: seguido del presupuesto total de salarios.

¡La belleza del patrón Composite es que Technology.getSalaryBudget() calcula automáticamente el total de todos los departamentos y empleados anidados; el código del cliente no necesita conocer la estructura interna!

Salida esperada:

+ Technology/
  + Engineering/
    - Alice ($80000)
    - Bob ($75000)
  + QA/
    - Carol ($65000)
Total Budget: $220000

Hoja de referencia

El patrón Composite permite tratar objetos individuales y grupos de objetos de manera uniforme, componiéndolos en estructuras de árbol donde tanto los elementos individuales como las colecciones comparten la misma interfaz.

El patrón consta de:

  • Componente: Una interfaz abstracta que define operaciones comunes
  • Hoja: Objetos individuales sin hijos
  • Compuesto: Contenedores que albergan otros componentes (hojas o compuestos)

Ejemplo: Sistema de archivos

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

La clase Folder puede contener tanto objetos File como otros objetos Folder. Cuando llamas a getSize() en una carpeta, calcula recursivamente el tamaño total de todo su contenido. El código cliente trata los archivos individuales y las jerarquías de carpetas completas de manera uniforme.

Pruébalo tú mismo

import 'organization.dart';

void main() {
  // TODO: Crear un departamento de Ingeniería
  // TODO: Añadir a Alice con un salario de 80000
  // TODO: Añadir a Bob con un salario de 75000
  
  // TODO: Crear un departamento de QA
  // TODO: Añadir a Carol con un salario de 65000
  
  // TODO: Crear un departamento de Tecnología
  // TODO: Añadir Ingeniería y QA como subdepartamentos
  
  // TODO: Llamar a display() en el departamento de Tecnología
  
  // TODO: Imprimir "Total Budget: $" seguido del presupuesto total de salarios
}
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