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

El Patrón Decorator: Añade funcionalidades sin tocar una sola línea del original

Envuelve, apila y extiende. La alternativa elegante a la explosión de subclases.

Escrito por domin el 19 de octubre de 2025 · Actualizado el 8 de febrero de 2026

🎁 El Patrón Decorator: envuelve, apila y extiende

¿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, CafeConLecheChocolateYCanela… y así hasta el infinito, el patrón Decorator te permite envolver la taza de café original con objetos “decoradores” (Leche, Chocolate, Canela…) 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. Como las capas de una cebolla: cada capa añade algo nuevo sin modificar las de dentro.

¿Qué tipo de patrón de diseño es el Decorator?


El problema que resuelve

Para entender por qué existe el Decorator, piensa en el problema de la explosión de subclases. Si tienes un café con 5 posibles extras (leche, chocolate, canela, vainilla, caramelo), las combinaciones posibles son 2⁵ = 32 subclases. Si añades un sexto extra, se duplican a 64. Eso es inviable.

Sin Decorator

Una subclase por cada combinación: CafeConLeche, CafeConChocolate, CafeConLecheYChocolate, CafeConLecheChocolateYCanela... Explosión exponencial de clases.

Con Decorator

Una clase base + un decorador por extra: CafeSimple, LecheDecorador, ChocolateDecorador, CanelaDecorador... Se combinan libremente apilando.

Con 5 extras, pasas de 32 subclases a 6 clases (1 base + 5 decoradores). Y puedes combinarlas como quieras en tiempo de ejecución. Eso es el poder del Decorator.

¿Qué problema principal resuelve el patrón Decorator?


¿Cuándo debería usarlo?

El patrón Decorator es ideal en estas situaciones:


Estructura del patrón

El Decorator se basa en la composición y en que tanto los objetos base como los decoradores implementan la misma interfaz. Estos son los participantes:

Component (Interfaz)

Define la interfaz que comparten el objeto base y los decoradores. El cliente trabaja con esta interfaz sin saber qué hay debajo.

Concrete Component

El objeto original que recibe la funcionalidad añadida. La "taza de café solo". Funciona perfectamente por sí solo.

Base Decorator

Clase abstracta que implementa la misma interfaz y envuelve un Component. Delega las llamadas al objeto envuelto por defecto.

Concrete Decorators

Cada decorador concreto extiende el Base Decorator y añade su funcionalidad antes o después de delegar al objeto envuelto.

¿Qué deben compartir el objeto base y los decoradores?


Implementación: el ejemplo del café

<?php

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

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

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

// 3. La Clase Abstracta Decorator (Base Decorator)
abstract class DecoradorCafe implements Cafe
{
    public function __construct(protected Cafe $cafe) {}

    public function getDescripcion(): string
    {
        return $this->cafe->getDescripcion();
    }

    public function getCosto(): float
    {
        return $this->cafe->getCosto();
    }
}

// 4. Decoradores Concretos
class LecheDecorador extends DecoradorCafe
{
    public function getDescripcion(): string
    {
        return parent::getDescripcion() . ", con Leche";
    }

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

class ChocolateDecorador extends DecoradorCafe
{
    public function getDescripcion(): string
    {
        return parent::getDescripcion() . ", con Chocolate";
    }

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

class CanelaDecorador extends DecoradorCafe
{
    public function getDescripcion(): string
    {
        return parent::getDescripcion() . ", con Canela";
    }

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

Ahora a usarlo apilando decoradores como capas:

// Café solo
$miCafe = new CafeSimple();
echo $miCafe->getDescripcion() . " → " . $miCafe->getCosto() . "€\n";
// Café Solo → 2€

// Le añadimos leche
$miCafe = new LecheDecorador($miCafe);
echo $miCafe->getDescripcion() . " → " . $miCafe->getCosto() . "€\n";
// Café Solo, con Leche → 2.50€

// Y chocolate (decorador sobre decorador)
$miCafe = new ChocolateDecorador($miCafe);
echo $miCafe->getDescripcion() . " → " . $miCafe->getCosto() . "€\n";
// Café Solo, con Leche, con Chocolate → 3.25€

// Y canela (otro decorador más)
$miCafe = new CanelaDecorador($miCafe);
echo $miCafe->getDescripcion() . " → " . $miCafe->getCosto() . "€\n";
// Café Solo, con Leche, con Chocolate, con Canela → 3.55€

Fíjate en lo potente que es: cada línea envuelve el objeto anterior en una capa nueva. Y en ningún momento se modifica CafeSimple, ni LecheDecorador, ni ninguna otra clase. Abierto a extensión, cerrado a modificación (OCP de SOLID).

En el ejemplo del café, ¿cuántas clases necesitas para 5 extras con Decorator vs sin él?


Ejemplo práctico: procesamiento de datos

En este ejemplo más real, aplicamos diferentes capas de procesamiento (compresión y cifrado) a un objeto de datos base:

<?php

interface ProcesadorDatos
{
    public function procesar(string $data): string;
}

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

abstract class DecoradorProcesamiento implements ProcesadorDatos
{
    public function __construct(protected ProcesadorDatos $envoltorio) {}

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

class CifradoDecorador extends DecoradorProcesamiento
{
    public function procesar(string $data): string
    {
        $datosCifrados = "[CIFRADO](" . $data . ")";
        return $this->envoltorio->procesar($datosCifrados);
    }
}

class CompresionDecorador extends DecoradorProcesamiento
{
    public function procesar(string $data): string
    {
        $datosComprimidos = "[COMPRIMIDO](" . $data . ")";
        return $this->envoltorio->procesar($datosComprimidos);
    }
}

class LogDecorador extends DecoradorProcesamiento
{
    public function procesar(string $data): string
    {
        echo "[LOG] Procesando datos de " . strlen($data) . " bytes\n";
        return $this->envoltorio->procesar($data);
    }
}

Ahora puedes combinar los decoradores en el orden que necesites:

$datos = "Información importante y confidencial.";

// Solo datos base
$base = new DatosBase();
echo $base->procesar($datos) . "\n";
// Datos Crudos: Información importante y confidencial.

// Cifrado + Base
$cifrado = new CifradoDecorador(new DatosBase());
echo $cifrado->procesar($datos) . "\n";
// Datos Crudos: [CIFRADO](Información importante y confidencial.)

// Log + Cifrado + Compresión + Base
$completo = new LogDecorador(
    new CifradoDecorador(
        new CompresionDecorador(
            new DatosBase()
        )
    )
);
echo $completo->procesar($datos) . "\n";
// [LOG] Procesando datos de 38 bytes
// Datos Crudos: [COMPRIMIDO]([CIFRADO](Información importante y confidencial.))

Fíjate que el orden importa. Primero se cifra, luego se comprime, luego se loguea. Cambiar el orden cambia el resultado. Es como las capas de la cebolla: se aplican de fuera hacia dentro.

¿El orden de los decoradores importa?


Ejemplo del mundo real: middleware HTTP

Si has trabajado con frameworks como Laravel, Symfony o Express, ya has usado el patrón Decorator sin saberlo. El sistema de middleware es un Decorator en toda regla:

<?php

interface HttpHandler
{
    public function handle(Request $request): Response;
}

class AppController implements HttpHandler
{
    public function handle(Request $request): Response
    {
        return new Response("Respuesta de la aplicación");
    }
}

abstract class Middleware implements HttpHandler
{
    public function __construct(protected HttpHandler $next) {}
}

class AuthMiddleware extends Middleware
{
    public function handle(Request $request): Response
    {
        if (!$request->hasValidToken()) {
            return new Response("No autorizado", 401);
        }
        // Si tiene token, pasa al siguiente
        return $this->next->handle($request);
    }
}

class LogMiddleware extends Middleware
{
    public function handle(Request $request): Response
    {
        $start = microtime(true);
        $response = $this->next->handle($request);
        $duration = microtime(true) - $start;
        echo "[LOG] {$request->getMethod()} {$request->getPath()} - {$duration}s\n";
        return $response;
    }
}

class CorsMiddleware extends Middleware
{
    public function handle(Request $request): Response
    {
        $response = $this->next->handle($request);
        $response->addHeader("Access-Control-Allow-Origin", "*");
        return $response;
    }
}

// Apilar middlewares (el orden define el pipeline)
$app = new LogMiddleware(
    new AuthMiddleware(
        new CorsMiddleware(
            new AppController()
        )
    )
);

$response = $app->handle($request);

Cada middleware es un decorador que envuelve al siguiente. La petición pasa por Log → Auth → CORS → Controller, y la respuesta vuelve en el sentido contrario. Si el Auth falla, ni siquiera llega al controlador. Esto es el Decorator aplicado a infraestructura real.

¿Qué sistema de los frameworks web es un ejemplo real del patrón Decorator?


Decorator en el día a día: dónde lo vas a encontrar

El patrón Decorator está por todas partes aunque no lo veas:

I/O Streams

En Java/PHP, los streams de I/O son decoradores puros: BufferedReader(FileReader(file)). Cada capa añade funcionalidad (buffer, encoding...).

Middleware HTTP

Laravel, Symfony, Express... Los middlewares son decoradores que envuelven la petición/respuesta añadiendo auth, logs, CORS, rate limiting...

Cache

Un CachedRepository que envuelve un Repository: si está en caché, lo devuelve; si no, llama al repositorio real. El cliente no se entera.

Logging

Envolver cualquier servicio con un decorador de logging para registrar todas las llamadas sin tocar el servicio original.

Validación

Un decorador que valida los datos de entrada antes de pasarlos al servicio real. Si la validación falla, ni siquiera llega al servicio.

Rate Limiting

Un decorador que cuenta las peticiones por usuario y las bloquea si superan el límite. El servicio decorado no sabe nada de esto.


Relación con los principios SOLID

El Decorator es un patrón que nace naturalmente cuando aplicas SOLID:

¿Qué principio SOLID cumple el Decorator al crear nuevos decoradores sin tocar los existentes?


Ventajas y desventajas

Ventajas
  • Flexibilidad: Añade o elimina responsabilidades en tiempo de ejecución.
  • Evita la explosión de subclases: N decoradores cubren 2^N combinaciones.
  • SRP: Cada decorador se centra en una única funcionalidad.
  • OCP: Nuevas funcionalidades = nuevos decoradores, sin tocar lo existente.
  • Transparencia: Misma interfaz, el cliente no nota la diferencia.
Desventajas
  • Muchos objetos pequeños: Puede resultar en muchas clases que solo delegan.
  • Configuración compleja: Envolver muchas veces complica la instanciación y el debugging.
  • El orden importa: Apilar decoradores en orden incorrecto puede dar resultados inesperados.
  • Identidad del objeto: Se pierde la clase concreta del objeto envuelto (solo se conoce la interfaz).

Decorator vs otros patrones

Es fácil confundir el Decorator con otros patrones, aquí van las diferencias clave:

PatrónPropósitoDiferencia con Decorator
AdapterCambia la interfaz de un objetoDecorator no cambia la interfaz, la mantiene igual
ProxyControla el acceso al objetoDecorator añade funcionalidad, no controla acceso
StrategyIntercambia algoritmos completosDecorator apila capas incrementales, no reemplaza
Chain of Resp.Pasa la petición hasta que alguien la manejeDecorator siempre ejecuta todos los eslabones de la cadena

¿Cuál es la diferencia principal entre Decorator y Adapter?


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 brutal 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 la “explosión de subclases” y te lo vas a encontrar en middleware, caché, logging, streams de I/O y muchos más sitios.

La clave: misma interfaz, funcionalidad apilable, objeto original intacto.

¡Un saludo y nos vemos en los bares! 🍻