Patrón Composite
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 80 de 91.
El Patrón Composite es un patrón de diseño estructural que permite componer objetos en estructuras de árbol y luego trabajar con estas estructuras como si fueran objetos individuales. Es perfecto para representar jerarquías de parte-todo donde se desea tratar a los objetos individuales y a los grupos de objetos de manera uniforme.
Considere un sistema de archivos. Una carpeta puede contener archivos y otras carpetas.
Cuando calculas el tamaño total, quieres llamar al mismo método tanto en archivos como en carpetas, y las carpetas deben sumar recursivamente su contenido. El Patrón Composite hace que esto sea fluido al darles tanto a las hojas (archivos) como a los compuestos (carpetas) la misma interfaz.
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
Ahora puedes construir estructuras de árbol y tratarlas de manera uniforme:
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize();
Resultado:
1810El código cliente llama a getSize() sin preocuparse de si está tratando con un solo archivo o con una estructura de carpetas profundamente anidada. El patrón maneja la recursividad internamente, facilitando el trabajo con jerarquías complejas.
Desafío
FácilVamos a construir un sistema de organigrama utilizando el Patrón Composite. Las empresas tienen una estructura jerárquica donde los departamentos pueden contener empleados y otros subdepartamentos — una estructura de árbol perfecta donde se desea calcular totales de manera uniforme en toda la organización.
Organizarás tu código en cuatro archivos:
OrganizationUnit.php— Define una interfazOrganizationUnitque implementarán tanto los empleados individuales como los departamentos. Debe tener dos métodos:getName(): stringygetSalary(): int. Esta interfaz común te permite tratar a los empleados individuales y a departamentos enteros de la misma manera al calcular costos.Employee.php— Incluye la interfaz OrganizationUnit y crea una claseEmployeeque la implemente. Un empleado es un nodo hoja en tu árbol de organización — tiene un nombre y un salario, pero no puede contener otras unidades. Usa la promoción de constructor para aceptar el nombre y el salario, e implementa ambos métodos de la interfaz.Department.php— Incluye la interfaz OrganizationUnit y crea una claseDepartmentque la implemente. Un departamento es un compuesto que puede contener tanto empleados como otros departamentos. Tu departamento debe:- Aceptar un nombre en su constructor
- Tener un método
add(OrganizationUnit $unit): voidpara añadir empleados o subdepartamentos - Implementar
getSalary()para devolver el salario total de todas las unidades que contiene (sumando recursivamente todo lo que hay dentro)
main.php— Incluye los archivos Employee y Department. Recibirás una entrada: una cadena JSON que representa la estructura de la organización.El JSON tendrá este formato:
{"Engineering": [{"name": "Alice", "salary": 80000}, {"name": "Bob", "salary": 75000}], "Sales": [{"name": "Carol", "salary": 65000}]}Cada clave es el nombre de un departamento, y su valor es un array de empleados con sus nombres y salarios.
Crea un
Departmentraíz llamado"Company". Para cada departamento en el JSON, crea unDepartment, añade sus empleados, luego añade ese departamento a la Company. Finalmente, imprime el salario total de la empresa en este formato:Total Salary: [amount]
La belleza del Patrón Composite es que llamar a getSalary() en el departamento Company calcula automáticamente la suma de todos los departamentos y empleados anidados — el código del cliente no necesita conocer la estructura interna.
Hoja de referencia
El Patrón Composite es un patrón de diseño estructural que compone objetos en estructuras de árbol y permite tratar objetos individuales y grupos de manera uniforme.
Es ideal para jerarquías de parte-todo donde se desea la misma interfaz tanto para los nodos hoja como para los nodos compuestos.
Componentes Clave
- Interfaz de Componente — Define operaciones comunes tanto para hojas como para compuestos
- Hoja (Leaf) — Representa objetos individuales que no pueden contener otros objetos
- Compuesto (Composite) — Contiene hijos (hojas u otros compuestos) e implementa operaciones delegando en los hijos
Ejemplo: Sistema de Archivos
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
Uso
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize(); // 1810
El cliente llama a getSize() de manera uniforme tanto en archivos como en carpetas. El compuesto maneja la recursividad internamente, haciendo que las jerarquías complejas sean fáciles de manejar.
Pruébalo tú mismo
<?php
require_once 'Employee.php';
require_once 'Department.php';
// Leer la entrada JSON
$input = trim(fgets(STDIN));
$data = (array)json_decode($input, true);
// TODO: Crear un Departamento raíz llamado "Company"
// TODO: Recorrer cada departamento en los datos JSON
// Para cada departamento:
// - Crear un nuevo objeto Department
// - Añadir cada empleado a ese departamento
// - Añadir el departamento a la Company
// TODO: Imprimir el salario total en el formato: Total Salary: [amount]
?>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 externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - Calculadora de Formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeResumen - Colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy