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.
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 abstractaOrganizationUnitcon un gettername, un métodogetSalaryBudget()que devuelva unint, y un métododisplay([String indent = ''])para mostrar la jerarquía. Construye una claseEmployee(la hoja) que implementeOrganizationUnit. Debe tener un nombre y un salario, dondegetSalaryBudget()devuelva el salario del empleado. Su métododisplay()debe imprimir[indent]- [name] ($[salary]). Luego, crea una claseDepartment(el compuesto) que también implementeOrganizationUnit. Debe tener un nombre y mantener una lista de hijos de tipoOrganizationUnit. Añade un métodoadd()para incluir empleados o subdepartamentos. SugetSalaryBudget()debe sumar recursivamente los presupuestos de todos los hijos. Sudisplay()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 de80000y'Bob'con un salario de75000. Crea un departamento'QA'con una empleada:'Carol'con un salario de65000. Ahora crea un departamento'Technology'y añade tanto'Engineering'como'QA'como subdepartamentos. Llama adisplay()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: $220000Hoja 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
}
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 POO
Archivos externosLibrerías e importacionesIntroducción a la POOClases vs. ObjetosLa palabra clave thisMétodosVariables de instanciaConceptos básicos de constructoresResumen - Calculadora sencilla4Seguridad de nulos
Introducción a la seguridad de nulosNulables vs No nulablesLos operadores ? y !Palabra clave Late y Null SafetyOperadores Null-awareNull Safety en clasesResumen - Sistema de perfil de usuario7Clases abstractas e interfaces
Clases abstractasMétodos abstractosInterfaces en DartInterfaces implícitasImplementar vs. ExtenderInterfaces múltiplesRepaso - Calculadora de figuras10Colecciones y Genéricos
Visión general de List, Set y MapColecciones con seguridad de tiposClases genéricasMétodos genéricosRestricciones genéricasIterable e IteratorResumen - Almacenamiento genérico2Constructores en Dart
Constructor por defectoConstructores nombradosListas de inicializaciónConstructores constantesConstructores de fábricaConstructores de redireccionamientoResumen - Constructor de formas5Encapsulamiento
Miembros públicos vs. privadosLa convención del prefijo _Privacidad a nivel de libreríaProfundidad en Getters y SettersOcultamiento de informaciónResumen - Registros de estudiantes8Mixins
Introducción a los MixinsCreación de MixinsUso de múltiples MixinsPalabra clave on en MixinsMixin vs HerenciaMixin vs InterfazResumen - Sistema Animal11Métodos especiales
Sobrescritura de toString()Sobrescritura de hashCode y ==Interfaz ComparableMétodo call()Sobrescritura de noSuchMethodResumen - Colección personalizada14Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy3Propiedades de clase
Miembros de instancia vs. estáticosCampos Final y ConstVariables LateMétodos y campos estáticosGetters y SettersResumen - Gestor de cuentas bancarias6Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosLa anotación @overrideLa palabra clave final en clasesConstructores y herenciaResumen - Jerarquía de empleados9Polimorfismo
Fundamentos del polimorfismoPolimorfismo mediante interfacesComprobación de tipos en tiempo de ejecuciónLos operadores is y asPalabra clave covariantResumen - Procesador de pagos12POO Asíncrona
Futures y async/awaitFundamentos de StreamsStream ControllersConstructores asíncronosAsincronía en métodos de claseRepaso - Data Fetcher15Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository