Patrón Decorator
El patrón Decorator agrega comportamiento a un objeto de forma dinámica envolviéndolo en otro que comparte su interfaz. Úsalo cuando quieras añadir características transversales —logging, caché, validación— sin subclasificar ni ensuciar el tipo base.
Resumen
El patrón Decorator envuelve un objeto dentro de otro que implementa la misma interfaz, añadiendo comportamiento en capas. A diferencia de la herencia, la decoración ocurre en tiempo de ejecución y se puede combinar libremente: el mismo servicio base puede envolverse con un logger, luego con un caché y luego con un retry, en cualquier orden. Es la base de cosas como los middlewares de Express y los higher-order components de React.
Cuándo usarlo
- Necesitas añadir preocupaciones transversales (logging, caché, auth, retry) sin modificar la clase envuelta.
- El conjunto de características que quieres combinar es abierto y varía por punto de uso.
- Subclasificar generaría una explosión combinatoria de clases (LoggedCachedRetryingService...).
Ejemplo
interface DataService {
fetch(id: string): Promise<string>;
}
class HttpDataService implements DataService {
async fetch(id: string) { return `data:${id}`; }
}
class LoggingDecorator implements DataService {
constructor(private readonly inner: DataService) {}
async fetch(id: string) {
console.log(`fetch start ${id}`);
const result = await this.inner.fetch(id);
console.log(`fetch end ${id}`);
return result;
}
}
class CachingDecorator implements DataService {
private cache = new Map<string, string>();
constructor(private readonly inner: DataService) {}
async fetch(id: string) {
const cached = this.cache.get(id);
if (cached) return cached;
const result = await this.inner.fetch(id);
this.cache.set(id, result);
return result;
}
}
const service = new LoggingDecorator(
new CachingDecorator(new HttpDataService())
);Ventajas
- Compone comportamiento en tiempo de ejecución sin tocar la clase original.
- Open/closed: añade nuevos decoradores sin modificar código existente.
- Cada preocupación vive en su propia clase — fácil de testear de forma aislada.
Desventajas
- Pilas de decoradores pueden ser difíciles de depurar (¿qué capa añadió ese comportamiento?).
- El orden de composición importa y es fácil de equivocar.
- La identidad se fragmenta — decorado !== original, lo que puede romper comparaciones.