GoF, p. 175 também chamado de Wrapper
Decorador
Decorator
Empilhe comportamentos em tempo de execução, envolvendo o objeto em camadas.
Anexar responsabilidades adicionais a um objeto dinamicamente. Decorators oferecem uma alternativa flexível ao uso de subclasses para estender funcionalidade.
Vestir-se em camadas
Está frio: você põe um suéter. Ficou mais frio e começou a chover: põe uma capa por cima. Cada peça “envolve” você e acrescenta uma propriedade, sem alterar seu corpo. E dá para tirar a capa quando parar de chover. Nenhuma dessas combinações exigiu criar uma pessoa nova “PessoaComSuéterECapa”.
Você precisa adicionar comportamentos opcionais e combináveis a objetos. Herança não serve: é estática (decide-se em tempo de compilação), a maioria das linguagens não tem herança múltipla, e N comportamentos combináveis geram 2^N subclasses.
Crie wrappers que implementam a MESMA interface do objeto envolvido, guardam uma referência a ele, delegam a chamada e acrescentam algo antes ou depois. Como a interface é a mesma, wrappers podem envolver outros wrappers, empilhando comportamentos.
- Subclasses nomeadas por combinação:
ServicoComLogEComCache. - Flags booleanas no construtor ligando/desligando comportamentos.
Estrutura
- Component
- Interface comum ao objeto real e aos decoradores.
- ConcreteComponent
- O objeto com o comportamento base.
- BaseDecorator
- Guarda a referência ao componente envolvido e delega tudo por padrão.
- ConcreteDecorator
- Sobrescreve métodos, faz o extra e chama o `super`.
Implementação
// ── Component ─────────────────────────────────────────
class ClienteHTTP { async get(url) { throw new Error('abstrato'); } }
// ── ConcreteComponent: o comportamento base ───────────
class ClienteBase extends ClienteHTTP {
async get(url) { return { url, corpo: `<dados de ${url}>`, ts: 'agora' }; }
}
// ── Decorador base: delega TUDO por padrão ────────────
class Decorador extends ClienteHTTP {
constructor(envolvido) { super(); this.envolvido = envolvido; }
async get(url) { return this.envolvido.get(url); }
}
// ── Decoradores concretos: cada um faz UMA coisa ──────
class ComLog extends Decorador {
async get(url) {
console.log(`→ GET ${url}`);
const r = await super.get(url); // antes / depois
console.log(`← 200 ${url}`);
return r;
}
}
class ComCache extends Decorador {
#cache = new Map();
async get(url) {
if (this.#cache.has(url)) { console.log(`⚡ cache ${url}`); return this.#cache.get(url); }
const r = await super.get(url);
this.#cache.set(url, r);
return r;
}
}
class ComRetry extends Decorador {
constructor(envolvido, tentativas = 3) { super(envolvido); this.tentativas = tentativas; }
async get(url) {
for (let i = 1; i <= this.tentativas; i++) {
try { return await super.get(url); }
catch (e) { if (i === this.tentativas) throw e; console.log(`↻ tentativa ${i + 1}`); }
}
}
}
// ── A montagem: a ORDEM das camadas importa ───────────
// log( cache( retry( base ) ) )
const cliente = new ComLog(new ComCache(new ComRetry(new ClienteBase())));
await cliente.get('/usuarios'); // → GET, busca, ← 200
await cliente.get('/usuarios'); // → GET, ⚡ cache, ← 200
// Cache POR FORA do log inverteria o comportamento — e é só reordenar. - java.io: new BufferedReader(new InputStreamReader(new FileInputStream(f)))
- Middlewares do Express/Koa — decoram o handler
- Higher-Order Components no React; decorators do Angular/NestJS
Consequências
- Estende comportamento sem criar subclasses.
- Adiciona e remove responsabilidades em tempo de execução.
- Combina vários comportamentos empilhando wrappers.
- Divide uma classe monolítica em camadas de responsabilidade única.
- Difícil remover um wrapper específico do meio da pilha.
- O comportamento depende da ORDEM da pilha — o que pode surpreender.
- A pilha de chamadas fica longa e a depuração, mais confusa.
- Você precisa acrescentar comportamento a objetos em tempo de execução sem quebrar o resto.
- Herança é impossível (classe final) ou geraria explosão combinatória.
- Você quer aspectos transversais (log, cache, retry, autenticação) desacoplados do núcleo.
- Só existe uma variação de comportamento e ela é permanente.
Relações com outros padrões
- Adapter
- Adapter muda a interface; Decorator a preserva e enriquece.
- Proxy
- Estrutura idêntica; Proxy gerencia o CICLO DE VIDA e o ACESSO do objeto real; Decorator é composto pelo cliente.
- Composite
- Decorator é um Composite degenerado, com um único filho e responsabilidade extra.
- Chain of Responsibility
- Ambos passam a chamada adiante; na Chain qualquer elo pode INTERROMPER, no Decorator todos executam.
- Strategy
- Decorator muda a pele do objeto; Strategy muda as tripas.
Verificação
Em `new ComLog(new ComCache(new ClienteBase()))`, quem executa primeiro ao chamar get()?
- ComLog — a camada mais externa recebe a chamada e decide quando delegar
- ClienteBase — o núcleo sempre roda primeiro
- ComCache — decoradores de cache têm prioridade
- A ordem é indefinida e depende da linguagem
A chamada entra pela camada mais externa e desce até o núcleo, voltando na ordem inversa — igual a uma pilha de middlewares. Por isso reordenar decoradores muda o comportamento.