Problema

Agregar funcionalidades a objetos dinámicamente sin alterar su estructura ni crear subclases.

Propósito

Envolver un objeto con capas adicionales de funcionalidad. Cada decorador añade una característica específica y puede combinarse con otros decoradores como capas de cebolla.

Concepto clave

Envoltorio en cadena: Cada decorador envuelve al objeto anterior, añadiendo su funcionalidad y delegando el resto. Es como poner calcetines: cada capa añade algo pero mantiene lo que había debajo.

Casos de uso comunes

  • Middleware web: autenticación + logging + compresión en capas
  • Streams: FileInputStream + Buffer + Compresión
  • Validación: required + email + longitud mínima
  • UI: botón + sombra + borde + animación
  • Texto: texto + negrita + cursiva + subrayado

¿Quién es quién en Decorator?

Actor Lo que realmente es Ejemplo Analogía
Component Interfaz común para originales y decoradores Coffee - define getDescription(), getCost() “Bebida” (interfaz)
ConcreteComponent Objeto base con funcionalidad básica SimpleCoffee - café sin nada Café simple (bebida básica)
BaseDecorator Decorador base que mantiene referencia CoffeeDecorator - delega al componente “Ingrediente” (base para añadir)
ConcreteDecorator Decoradores que añaden funcionalidad MilkDecorator, SugarDecorator Leche, Azúcar (ingredientes específicos)

Clave: Cada decorador ES-UN Component Y TIENE-UN Component (composición + herencia)

Diagrama

classDiagram
    namespace Decorator {
        class Component {
            <<interface>>
            +operation()
        }
        
        class ConcreteComponent {
            +operation()
        }
        
        class BaseDecorator {
            -component: Component
            +operation()
        }
        
        class ConcreteDecoratorA {
            +operation()
            +extraBehaviorA()
        }
        
        class ConcreteDecoratorB {
            +operation()
            +extraBehaviorB()
        }
    }
    
    Component <|.. ConcreteComponent
    Component <|.. BaseDecorator
    BaseDecorator <|-- ConcreteDecoratorA
    BaseDecorator <|-- ConcreteDecoratorB
    BaseDecorator --> Component

Ejemplo práctico

classDiagram
    namespace CoffeeExample {
        class Coffee {
            <<interface>>
            +getDescription() String
            +getCost() double
        }
        
        class SimpleCoffee {
            +getDescription() String
            +getCost() double
        }
        
        class CoffeeDecorator {
            -coffee: Coffee
            +getDescription() String
            +getCost() double
        }
        
        class MilkDecorator {
            +getDescription() String
            +getCost() double
        }
        
        class SugarDecorator {
            +getDescription() String
            +getCost() double
        }
        
        class WhipDecorator {
            +getDescription() String
            +getCost() double
        }
    }
    
    Coffee <|.. SimpleCoffee
    Coffee <|.. CoffeeDecorator
    CoffeeDecorator <|-- MilkDecorator
    CoffeeDecorator <|-- SugarDecorator
    CoffeeDecorator <|-- WhipDecorator
    CoffeeDecorator --> Coffee

Ejemplo de envoltorio

Cliente solicita: getCost()

┌─────────────────────────────────────┐
│ WhipDecorator (+0.7)                │
│ ┌─────────────────────────────────┐ │
│ │ SugarDecorator (+0.2)           │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ MilkDecorator (+0.5)        │ │ │
│ │ │ ┌─────────────────────────┐ │ │ │
│ │ │ │ SimpleCoffee (2.0)      │ │ │ │
│ │ │ └─────────────────────────┘ │ │ │
│ │ └─────────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘

Flujo: 2.0 → 2.5 → 2.7 → 3.4

Ventajas

  • Combinaciones infinitas: Puedes mezclar decoradores como quieras
  • Responsabilidad única: Cada decorador hace una sola cosa
  • Extensible: Agregar nuevos decoradores sin tocar código existente
  • Dinámico: Añadir/quitar funcionalidades en tiempo de ejecución

Desventajas

  • Muchos objetos pequeños: Puede ser confuso rastrear todas las capas
  • Orden importa: encrypt(compress(data))compress(encrypt(data))
  • Performance: Cada capa añade overhead
  • Debugging complejo: Difícil saber en qué capa está el problema

Cuándo usar

  • Tienes muchas combinaciones posibles de funcionalidades
  • Quieres añadir/quitar características dinámicamente
  • Cada funcionalidad es independiente y reutilizable
  • La herencia crearía demasiadas subclases

Cuándo NO usar

  • Solo necesitas 2-3 variaciones (usa herencia simple)
  • Las funcionalidades están fuertemente acopladas
  • La performance es crítica
  • El orden de aplicación no importa (considera Strategy)