🚨 ¡Nueva review! ✨ Mi ratón favorito para programar: el Logitech MX Master 3S . ¡Échale un ojo! 👀

Patrón de diseño: Decorator

Añadiendo funcionalidades sin herencia, like a pro

Escrito por domin el 19/10/2001

🎁 Patrón de diseño: Decorator

¿Qué es?

El patrón Decorator es un patrón de diseño del tipo estructural que permite añadir dinámicamente responsabilidades adicionales a un objeto que ya existe y está funcionando. Es una alternativa flexible a la herencia para extender la funcionalidad.

Imagina que tienes un objeto base, como una taza de café solo. En lugar de crear subclases como CafeConLeche, CafeConChocolate, CafeConLecheYChocolate, y así sucesivamente, el patrón Decorator te permite envolver la taza de café original con objetos “decoradores” (Leche, Chocolate, etc.) que añaden la nueva funcionalidad.

Cada decorador envuelve al objeto original y/o a otros decoradores, agregando un comportamiento antes o después de llamar a la funcionalidad del objeto envuelto.

¿Cuándo debería usarlo?

El patrón de diseño Decorator es ideal en las siguientes situaciones:

Implementación básica en PHP

El Decorator se basa en la composición y en el principio de que los objetos base y los decoradores implementan la misma interfaz (o extienden la misma clase abstracta).

<?php

// 1. La Interfaz Común (Componente)
interface Cafe
{
    public function getDescripcion(): string;
    public function getCosto(): float;
}

// 2. El Objeto Base (Componente Concreto)
class CafeSimple implements Cafe
{
    public function getDescripcion(): string
    {
        return "Café Solo";
    }

    public function getCosto(): float
    {
        return 2.00;
    }
}

// 3. La Clase Abstracta Decorator (Decorator)
abstract class DecoradorCafe implements Cafe
{
    public function __construct(protected Cafe $cafe) {}
    
    // Las llamadas se delegan al objeto envuelto por defecto
    public function getDescripcion(): string
    {
        return $this->cafe->getDescripcion();
    }
    
    public function getCosto(): float
    {
        return $this->cafe->getCosto();
    }
}

// 4. Decorador Concreto: Leche (Añade funcionalidad)
class LecheDecorador extends DecoradorCafe
{
    public function getDescripcion(): string
    {
        // Añade su propia descripción
        return parent::getDescripcion() . ", con Leche";
    }

    public function getCosto(): float
    {
        // Añade su propio costo
        return parent::getCosto() + 0.50;
    }
}

// 5. Decorador Concreto: Chocolate (Añade funcionalidad)
class ChocolateDecorador extends DecoradorCafe
{
    public function getDescripcion(): string
    {
        return parent::getDescripcion() . ", con Chocolate";
    }

    public function getCosto(): float
    {
        return parent::getCosto() + 0.75;
    }
}

// Uso del patrón: Apilando funcionalidades
$miCafe = new CafeSimple();
echo "== Cafe Base ==\n";
echo "Descripción: " . $miCafe->getDescripcion() . "\n";
echo "Costo: " . $miCafe->getCosto() . "€\n\n";

// Lo envolvemos en Leche
$miCafe = new LecheDecorador($miCafe);
echo "== Cafe con Leche ==\n";
echo "Descripción: " . $miCafe->getDescripcion() . "\n";
echo "Costo: " . $miCafe->getCosto() . "€\n\n";

// Lo envolvemos en Chocolate (decorador sobre decorador)
$miCafe = new ChocolateDecorador($miCafe);
echo "== Moka (Leche + Chocolate) ==\n";
echo "Descripción: " . $miCafe->getDescripcion() . "\n";
echo "Costo: " . $miCafe->getCosto() . "€\n";

Ejemplo práctico: Procesamiento de Datos

En este ejemplo, aplicamos diferentes capas de procesamiento (compresión y cifrado) a un objeto de datos base.

<?php

// Interfaz Componente: Define la operación
interface ProcesadorDatos {
    public function procesar(string $data): string;
}

// Componente Concreto: Datos simples
class DatosBase implements ProcesadorDatos {
    public function procesar(string $data): string {
        return "Datos Crudos: " . $data;
    }
}

// Clase Abstracta Decorator
abstract class DecoradorProcesamiento implements ProcesadorDatos {
    protected ProcesadorDatos $envoltorio;

    public function __construct(ProcesadorDatos $envoltorio) {
        $this->envoltorio = $envoltorio;
    }

    public function procesar(string $data): string {
        return $this->envoltorio->procesar($data); // Delega la llamada
    }
}

// Decorador Concreto: Cifrado
class CifradoDecorador extends DecoradorProcesamiento {
    public function procesar(string $data): string {
        // Aplica el cifrado ANTES de delegar
        $datosCifrados = "🔒(" . $data . ")🔒";
        return $this->envoltorio->procesar($datosCifrados);
    }
}

// Decorador Concreto: Compresión
class CompresionDecorador extends DecoradorProcesamiento {
    public function procesar(string $data): string {
        // Aplica la compresión ANTES de delegar
        $datosComprimidos = "📦[" . $data . "]📦";
        return $this->envoltorio->procesar($datosComprimidos);
    }
}


// Uso del sistema
$datos = "Hola mundo, esto son datos importantes.";

// Base sin decoración
$base = new DatosBase();
echo "1. Sin Decorar: " . $base->procesar($datos) . "\n";

// Decorar con Cifrado
$cifrado = new CifradoDecorador(new DatosBase());
echo "2. Solo Cifrado: " . $cifrado->procesar($datos) . "\n";

// Decorar con Cifrado Y Compresión (orden importa)
$cifradoComprimido = new CompresionDecorador(
    new CifradoDecorador(
        new DatosBase()
    )
);
echo "3. Cifrado + Compresión: " . $cifradoComprimido->procesar($datos) . "\n";

// Salida:
// 1. Sin Decorar: Datos Crudos: Hola mundo, esto son datos importantes.
// 2. Solo Cifrado: Datos Crudos: 🔒(Hola mundo, esto son datos importantes.)🔒
// 3. Cifrado + Compresión: Datos Crudos: 📦[🔒(Hola mundo, esto son datos importantes.)🔒]📦

Ventajas y desventajas

Ventajas:

Desventajas:

Conclusión

El patrón Decorator es una herramienta estructural elegante para extender funcionalidades sin recurrir a la herencia. Al usar la composición y la delegación, ofrece una flexibilidad superior para combinar y apilar comportamientos.

Úsalo cuando te enfrentes a un problema donde una subclase por cada variante o combinación de características resulte en una jerarquía de clases inmanejable. Es la solución ideal para el problema de “la explosión de subclases”.

¡Un saludo y nos vemos en los bares! 🍻