Pular para o conteúdo
GoF Padrões de Projeto Estruturais
Estrutural escopo de objeto
GoF, p. 175 também chamado de Wrapper

Decorador

Decorator

Empilhe comportamentos em tempo de execução, envolvendo o objeto em camadas.

Complexidade média Frequência de uso muito comum
Intençãocomo o livro define

Anexar responsabilidades adicionais a um objeto dinamicamente. Decorators oferecem uma alternativa flexível ao uso de subclasses para estender funcionalidade.

Analogiauma imagem do mundo real

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”.

O problema

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.

A solução

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.

Sintomascomo reconhecer no seu código
  • Subclasses nomeadas por combinação: ServicoComLogEComCache.
  • Flags booleanas no construtor ligando/desligando comportamentos.

Estrutura

envolve ↺Client«interface»FonteDeDados+ escrever(d)+ ler()ArquivoEmDiscoDecoradorBase- envolvido+ escrever(d)CriptografiaCompressao
Fig. 9 — Decorator herda / implementa cria usa / contém
Ver este diagrama sendo desenhado, traço a traço
Participantes
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

Listagem 9 Camadas empilháveis sobre um cliente HTTP
// ── 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.
Na práticaonde ele já existe
  • 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

A favor
  • 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.
Contra
  • 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.
Use quando
  • 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.
Evite quando
  • Só existe uma variação de comportamento e ela é permanente.

Relações com outros padrões

parecido / fácil de confundir
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

Questãoa resposta está marcada

Em `new ComLog(new ComCache(new ClienteBase()))`, quem executa primeiro ao chamar get()?

  1. ComLog — a camada mais externa recebe a chamada e decide quando delegar
  2. ClienteBase — o núcleo sempre roda primeiro
  3. ComCache — decoradores de cache têm prioridade
  4. 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.