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!
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