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 Decorator es un patrón de diseño estructural que te permite añadir dinámicamente nuevos comportamientos a los objetos envolviéndolos en objetos envoltorio 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, después añades extras como leche, azúcar o crema batida.
Cada adición envuelve el café original y añade información 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 el doble de leche? Solo envuélvelo dos veces.
Desafío
FácilConstruyamos un sistema de pedidos de pizza usando el Patrón Decorador. Comenzarás con una pizza básica y añadirás ingredientes dinámicamente: cada ingrediente envuelve la pizza y añade elementos a su descripción y precio, igual que en el ejemplo del café de la lección.
Organizarás tu código en cuatro archivos:
Pizza.php: Define una interfazPizzaque establece el contrato para todas las pizzas y los ingredientes. Debe tener dos métodos:getDescription(): stringygetPrice(): float.BasePizza.php: Incluye la interfaz Pizza y crea una claseMargheritaPizzaque la implementa. 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 implementan. 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 que se deben añadir (por ejemplo,"cheese,pepperoni"o"mushroom,cheese,cheese").Comienza con una
MargheritaPizza. Para cada ingrediente de la entrada, envuelve tu pizza con el decorador correspondiente:"cheese"se envuelve conCheeseTopping"pepperoni"se envuelve conPepperoniTopping"mushroom"se envuelve conMushroomTopping
Después de aplicar todos los ingredientes, imprime la descripción y el precio finales de la pizza con este formato:
[description] = $[price]Da formato al precio con dos decimales.
Observa cómo se puede aplicar el mismo ingrediente varias veces: pedir queso doble simplemente significa envolver dos veces con CheeseTopping. ¡Esta flexibilidad es el poder del Patrón Decorador!
Pruébalo tú mismo
<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';
// Leer la entrada - lista de toppings separados por comas
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);
// Empezar con una base MargheritaPizza
$pizza = new MargheritaPizza();
// TODO: Recorrer cada topping y envolver la pizza con el decorador apropiado
// - "cheese" -> envolver con CheeseTopping
// - "pepperoni" -> envolver con PepperoniTopping
// - "mushroom" -> envolver con MushroomTopping
// TODO: Imprimir el resultado en 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 la POO
Archivos externosIntroducción a la POOClases frente a objetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Repaso: calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasRepaso - Jerarquía de empleados7Encapsulación
Público, protegido, privadoModificadores de acceso en profundidadGetters y settersOcultación de la informaciónPromoción del constructor (8.0)Repaso: sistema de registros de estudiantes10Conceptos avanzados de OOP
Composición vs. herenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibras (PHP 8.1)Análisis profundo de la clonación de objetosGeneradores e iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Namespaces y carga automática
Introducción a los NamespacesLa palabra clave useEstándar de carga automática PSR-4Autoloader de ComposerRepaso: proyecto organizado5Interfaces y contratos
Introducción a las interfacesImplementación de interfacesImplementación de múltiples interfacesInterfaces vs. clase abstractaTipado con interfacesRepaso - 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 __unserializeRepaso: 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)Repaso: gestor de cuentas bancarias6Polimorfismo
Repaso de la sobrescritura de métodosPolimorfismo mediante interfacesAnotaciones de tipo y tipos uniónEnlace estático tardíoRepaso: procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos entre TraitsMétodos abstractos en TraitsTraits frente a la herencia12Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón StrategyPractica por tu cuenta: Compilador de PHP online