Patrón Decorator
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 77 de 91.
El Patrón Decorador es un patrón de diseño estructural que permite añadir nuevos comportamientos a los objetos de forma dinámica envolviéndolos en objetos envoltorios especiales llamados decoradores. A diferencia de la herencia, que añade comportamiento en tiempo de compilación, los decoradores añaden comportamiento en tiempo de ejecución.
Imagina una cafetería. Empiezas con un café básico y luego añades extras como leche, azúcar o crema batida.
Cada adición envuelve al café original y se suma a su descripción y costo. La idea clave es que tanto el café base como los decoradores comparten la misma interfaz.
<?php
interface Coffee {
public function getDescription(): string;
public function getCost(): float;
}
class SimpleCoffee implements Coffee {
public function getDescription(): string {
return "Simple Coffee";
}
public function getCost(): float {
return 2.00;
}
}
Los decoradores implementan la misma interfaz y envuelven otro objeto Coffee:
<?php
class MilkDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Milk";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.50;
}
}
class SugarDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Sugar";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.25;
}
}
$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);
echo $coffee->getDescription() . " = $" . $coffee->getCost();
Salida:
Simple Coffee, Milk, Sugar = $2.75Cada decorador envuelve al objeto anterior, formando una cadena. El patrón permite combinar comportamientos en cualquier orden y cantidad sin crear una clase para cada combinación posible. ¿Necesitas leche doble? Simplemente envuélvelo dos veces.
Desafío
FácilVamos a construir un sistema de pedido de pizzas utilizando el Patrón Decorator. Comenzarás con una pizza básica y añadirás ingredientes dinámicamente; cada ingrediente envuelve la pizza y se suma a su descripción y precio, tal como el ejemplo del café de la lección.
Organizarás tu código en cuatro archivos:
Pizza.php— Define una interfazPizzaque establezca el contrato para todas las pizzas e ingredientes. Debe tener dos métodos:getDescription(): stringygetPrice(): float.BasePizza.php— Incluye la interfaz Pizza y crea una claseMargheritaPizzaque la implemente. Esta es tu pizza base con una descripción de"Margherita"y un precio de8.00.Toppings.php— Incluye la interfaz Pizza y crea tres clases decoradoras que la implementen. Cada decorador envuelve un objetoPizza(acéptalo en el constructor) y mejora tanto la descripción como el precio:CheeseTopping— Añade", Extra Cheese"a la descripción y1.50al precioPepperoniTopping— Añade", Pepperoni"a la descripción y2.00al precioMushroomTopping— Añade", Mushrooms"a la descripción y1.25al precio
main.php— Incluye los archivos BasePizza y Toppings. Recibirás una entrada: una lista de ingredientes separados por comas para añadir (por ejemplo,"cheese,pepperoni"o"mushroom,cheese,cheese").Comienza con una
MargheritaPizza. Para cada ingrediente en la entrada, envuelve tu pizza con el decorador apropiado:"cheese"envuelve conCheeseTopping"pepperoni"envuelve conPepperoniTopping"mushroom"envuelve conMushroomTopping
Después de aplicar todos los ingredientes, imprime la descripción y el precio final de la pizza en este formato:
[description] = $[price]Formatea el precio con dos decimales.
Observa cómo el mismo ingrediente se puede aplicar varias veces: pedir queso doble simplemente significa envolver con CheeseTopping dos veces. ¡Esta flexibilidad es el poder del Patrón Decorator!
Hoja de referencia
El Patrón Decorador es un patrón de diseño estructural que añade comportamientos a los objetos de forma dinámica envolviéndolos en objetos decoradores en tiempo de ejecución.
Tanto el objeto base como los decoradores implementan la misma interfaz:
<?php
interface Coffee {
public function getDescription(): string;
public function getCost(): float;
}
class SimpleCoffee implements Coffee {
public function getDescription(): string {
return "Simple Coffee";
}
public function getCost(): float {
return 2.00;
}
}
Los decoradores envuelven a otro objeto del mismo tipo de interfaz:
<?php
class MilkDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Milk";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.50;
}
}
class SugarDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Sugar";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.25;
}
}
Encadena decoradores para combinar comportamientos:
$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);
echo $coffee->getDescription() . " = $" . $coffee->getCost();
// Salida: Simple Coffee, Milk, Sugar = $2.75
Los decoradores pueden aplicarse varias veces y en cualquier orden sin necesidad de crear clases para cada combinación.
Pruébalo tú mismo
<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';
// Leer la entrada - lista de ingredientes separados por comas
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);
// Comenzar con una MargheritaPizza base
$pizza = new MargheritaPizza();
// TODO: Recorrer cada ingrediente y envolver la pizza con el decorador adecuado
// - "cheese" -> envolver con CheeseTopping
// - "pepperoni" -> envolver con PepperoniTopping
// - "mushroom" -> envolver con MushroomTopping
// TODO: Imprimir el resultado en el formato: [description] = $[price]
// Formatear el precio a 2 decimales usando number_format()
?>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