Saltar al contenido

Decorator

Un patrón estructural para sumarle responsabilidades a un objeto en tiempo de ejecución, envolviéndolo en otros que respetan su misma interfaz.

6 min. de lectura

También llamado: Wrapper

Decorator es un patrón estructural que permite agregarle responsabilidades nuevas a un objeto dinámicamente, envolviéndolo en objetos decoradores compatibles con su misma interfaz.

El problema

El precio final de un producto en AndesShop se arma combinando varios ajustes: puede sumar un cargo por envoltorio de regalo, puede sumar un cargo por envío express, puede restar un descuento por cupón, y siempre suma impuestos. Cualquier combinación de estos ajustes es válida al mismo tiempo. Resolverlo con subclases (GiftWrappedExpressDiscountedPrice, ExpressDiscountedPrice, GiftWrappedPrice…) requeriría una subclase por cada combinación posible — y cada ajuste nuevo que se agregue duplica ese número.

La solución

Decorator resuelve esto envolviendo el objeto base en una cadena de decoradores, cada uno de los cuales implementa la misma interfaz que el objeto original y le agrega su propio ajuste antes o después de delegar al objeto que envuelve. Cualquier combinación de ajustes es simplemente una combinación distinta de envoltorios, sin necesidad de una clase por cada mezcla.

Estructura de Decorator: el decorador implementa la misma interfaz que decora, por eso se puede apilar sobre otro decorador.

Ejemplo en Java

interface PricedItem {
    double getPrice();
}

class BaseProduct implements PricedItem {
    private final double basePrice;

    public BaseProduct(double basePrice) {
        this.basePrice = basePrice;
    }

    public double getPrice() {
        return basePrice;
    }
}

// Decorator base: envuelve un PricedItem y delega en él
abstract class PriceDecorator implements PricedItem {
    protected final PricedItem wrapped;

    protected PriceDecorator(PricedItem wrapped) {
        this.wrapped = wrapped;
    }
}

class GiftWrapDecorator extends PriceDecorator {
    public GiftWrapDecorator(PricedItem wrapped) { super(wrapped); }

    public double getPrice() {
        return wrapped.getPrice() + 5.0; // cargo fijo por envoltorio
    }
}

class ExpressShippingDecorator extends PriceDecorator {
    public ExpressShippingDecorator(PricedItem wrapped) { super(wrapped); }

    public double getPrice() {
        return wrapped.getPrice() + 12.0; // cargo fijo por envío express
    }
}
// Client code: cualquier combinación es solo anidar decoradores
PricedItem item = new ExpressShippingDecorator(
    new GiftWrapDecorator(
        new BaseProduct(89.90)
    )
);

System.out.println(item.getPrice()); // 89.90 + 5.0 + 12.0

Cuándo usarlo

  • Cuando necesitás agregar o quitar responsabilidades a objetos individuales en tiempo de ejecución, sin afectar a otros objetos de la misma clase.
  • Cuando extender por herencia generaría una explosión de subclases para cubrir todas las combinaciones posibles.

Cuándo evitarlo

Con pocas combinaciones posibles y estables, un conjunto pequeño y explícito de clases puede ser más fácil de leer que una cadena de decoradores anidados.

Ventajas y desventajas

VentajasDesventajas
Permite combinar responsabilidades sin generar una subclase por combinaciónPuede ser difícil de leer cuando la cadena de decoradores crece mucho
Respeta el principio de responsabilidad única: cada decorador hace una sola cosaEl orden en que se aplican los decoradores puede importar y no siempre es obvio
Sigue el principio de abierto/cerrado: se agregan decoradores nuevos sin tocar los existentesQuitar un decorador específico del medio de la cadena no es sencillo

Relación con otros patrones

  • Se diferencia de Adapter en el propósito: Adapter cambia la interfaz de un objeto; Decorator mantiene la misma interfaz y solo agrega comportamiento.
  • Se diferencia de Composite en la forma: Composite ramifica en varios hijos; Decorator siempre envuelve un único objeto.
  • Proxy también envuelve un objeto detrás de la misma interfaz, pero para controlar el acceso a él, no para sumarle comportamiento.

Te sirvió, compartilo

// ¿te sirvió?
// compartilo