Menu
Coddy logo textTech

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.75

Cada 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.

challenge icon

Desafío

Fácil

Vamos 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 interfaz Pizza que establezca el contrato para todas las pizzas e ingredientes. Debe tener dos métodos: getDescription(): string y getPrice(): float.
  • BasePizza.php — Incluye la interfaz Pizza y crea una clase MargheritaPizza que la implemente. Esta es tu pizza base con una descripción de "Margherita" y un precio de 8.00.
  • Toppings.php — Incluye la interfaz Pizza y crea tres clases decoradoras que la implementen. Cada decorador envuelve un objeto Pizza (acéptalo en el constructor) y mejora tanto la descripción como el precio:
    • CheeseTopping — Añade ", Extra Cheese" a la descripción y 1.50 al precio
    • PepperoniTopping — Añade ", Pepperoni" a la descripción y 2.00 al precio
    • MushroomTopping — Añade ", Mushrooms" a la descripción y 1.25 al 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 con CheeseTopping
    • "pepperoni" envuelve con PepperoniTopping
    • "mushroom" envuelve con MushroomTopping

    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()

?>
quiz iconPonte a prueba

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