Introdução
Seção intitulada “Introdução”O Decorator é um padrão de projeto estrutural que permite adicionar responsabilidades a um objeto dinamicamente, sem alterar o objeto nem criar uma classe para cada combinação. A técnica é envolver o objeto em outro que mantém a mesma interface e soma o próprio comportamento.
Ele aparece quando um sistema precisa combinar funcionalidades opcionais sobre uma base comum: um café com complementos, um serviço que pode ganhar log e cache, uma imagem que pode receber vários filtros. O Decorator evita que essas combinações virem uma explosão de subclasses.
Problema
Seção intitulada “Problema”Uma cafeteria vende bebidas e complementos combináveis. Por herança, cada combinação viraria uma subclasse nova:
CafeComLeite,CafeComChantilly,CafeComLeiteComChantilly…
Com N complementos, o número de combinações explode (2ᴺ). Além disso, a herança é estática:
- a “decoração” fica fixa em tempo de compilação
- mudar o preço base exige revisar todas as combinações
- não dá para montar a mesma bebida com ordens diferentes
- cada subclasse duplica a lógica de cálculo e de descrição
Solução
Seção intitulada “Solução”O Decorator resolve isso envolvendo o objeto em camadas:
- Defina uma interface comum (o
Component). - Crie o componente concreto: o objeto base, que implementa o comportamento.
- Crie o decorator: uma classe que também implementa a interface, guarda uma referência ao objeto interno e delega a ele.
- Crie decorators concretos que adicionam comportamento antes ou depois de delegar.
Cada camada se conecta como uma cadeia: o objeto externo delega ao interno e acrescenta algo. O cliente enxerga sempre a mesma interface, então pode empilhar quantas camadas quiser — em tempo de execução.
Analogia
Seção intitulada “Analogia”Pense em uma pizza montada. A massa é a base; os ingredientes são adicionados por cima. O “queijo extra” ou a “borda recheada” envolvem a pizza sem mudar o que ela é. No fim, continua sendo uma pizza — só que “decorada”.
Do outro lado, existe uma limitação natural: depois de muitas camadas, é difícil “enxergar” a massa original por baixo. No código, isso aparece quando precisamos acessar um comportamento específico do objeto interno após vários decorators.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use Decorator quando:
- você precisa adicionar responsabilidades individualmente e em combinações
- a herança geraria explosão de subclasses ou é estática demais
- quer adicionar comportamento em tempo de execução, com ordem livre
- deseja compor funcionalidades (log, cache, compressão, cifragem) sem tocar na base
Exemplos reais em software moderno:
- streams do
java.io(BufferedInputStreamem volta deFileInputStream) Collections.unmodifiableList,Collections.synchronizedList- filtros de servlets, middlewares e interceptadores
- adicionar cache, log, retry ou métricas a um serviço
Anti-padrão / Mau uso
Seção intitulada “Anti-padrão / Mau uso”Decorator pode ser mal utilizado quando a equipe o aplica sem necessidade. Os erros mais comuns são:
- usar decorators quando uma subclasse simples resolveria (variação fixa, sem combinação)
- empilhar camadas que dependem da ordem sem deixar isso explícito
- criar decorators que precisam acessar o objeto interno, gerando
instanceofe casts - decorar objetos demais e dificultar o rastreamento do comportamento
As consequências costumam ser muitas classes pequenas, dificuldade de depurar e um design mais complexo do que a simples extensão por herança resolveria.
Decorator funciona bem quando há combinação dinâmica de responsabilidades sobre uma interface estável.
Como implementar
Seção intitulada “Como implementar”Uma forma incremental de implementar Decorator é:
- Identifique a interface comum que o cliente já usa.
- Implemente o componente concreto com o comportamento base.
- Crie o decorator abstrato, que guarda uma referência ao componente e delega.
- Crie decorators concretos, adicionando responsabilidade antes ou depois da delegação.
- No cliente, monte a cadeia envolvendo o objeto nos decorators desejados.
Antes de começar, vale perguntar: as responsabilidades são combináveis em tempo de execução? A interface é estável? Se sim, Decorator é um forte candidato.
Exemplo em código
Seção intitulada “Exemplo em código”No exemplo abaixo, uma cafeteria soma complementos a uma bebida.
interface Bebida { String getDescricao(); double custo();}
class Cafe implements Bebida { @Override public String getDescricao() { return "Café"; }
@Override public double custo() { return 5.0; }}
abstract class Complemento implements Bebida { protected final Bebida bebida;
public Complemento(Bebida bebida) { this.bebida = bebida; }}
class Leite extends Complemento { public Leite(Bebida bebida) { super(bebida); }
@Override public String getDescricao() { return bebida.getDescricao() + " com leite"; }
@Override public double custo() { return bebida.custo() + 1.5; }}
class Chantilly extends Complemento { public Chantilly(Bebida bebida) { super(bebida); }
@Override public String getDescricao() { return bebida.getDescricao() + " com chantilly"; }
@Override public double custo() { return bebida.custo() + 2.0; }}Nesse código, Cafe é o componente concreto e Complemento é o decorator abstrato. Leite e Chantilly são decorators concretos: cada um delega ao objeto interno e soma descrição e custo.
- combinações montadas em tempo de execução, sem classes novas
- novos decorators entram sem tocar no componente base (OCP)
- cada responsabilidade fica isolada em um decorator (SRP)
- as camadas podem ser empilhadas na ordem desejada
Contras
Seção intitulada “Contras”- muitos objetos pequenos e camadas no sistema
- a ordem dos decorators pode afetar o resultado
- difícil inspecionar o objeto real sob as camadas
- remover um complemento exige reconstruir a cadeia
Relações com outros padrões/conceitos
Seção intitulada “Relações com outros padrões/conceitos”O Decorator se relaciona com outros padrões importantes:
- Composite: ambos são estruturais e usam composição. Composite agrega vários filhos formando uma árvore; Decorator envolve um objeto formando uma cadeia. O Decorator pode ser visto como um Composite “degenerado”, com um único filho.
- Adapter: muda a interface de um objeto; o Decorator mantém a interface e adiciona comportamento.
- Strategy: muda o comportamento interno (algoritmo); o Decorator muda o comportamento externo, envolvendo o objeto.
- Factory Method / Abstract Factory: costumam trabalhar junto com Decorator para montar a cadeia de decorators.
Implementações alternativas (quando aplicável)
Seção intitulada “Implementações alternativas (quando aplicável)”Em muitas linguagens o Decorator aparece de outras formas:
- em linguagens com suporte a decorators de linguagem (Python, TypeScript), a mesma ideia pode ser expressa de forma mais declarativa
BufferedReader/BufferedWriterno Java empilham funcionalidade sobre um stream base- middlewares e interceptadores (como filtros de servlets) aplicam a mesma lógica de cadeia sem herança
Independente da forma, a essência é a mesma: envolver um objeto com outro da mesma interface para somar responsabilidades dinamicamente.
Exemplo completo
Seção intitulada “Exemplo completo”Agora veja um caso mais realista com um serviço que precisa ganhar log e cache sem mudar a interface usada pelos consumidores.
interface ServicoProduto { Produto buscar(int id);}
class ServicoProdutoBase implements ServicoProduto { @Override public Produto buscar(int id) { System.out.println("Buscando produto " + id + " no banco..."); return new Produto(id, "Cafeteira", 299.0); }}
class LogDecorator implements ServicoProduto { private final ServicoProduto interno;
public LogDecorator(ServicoProduto interno) { this.interno = interno; }
@Override public Produto buscar(int id) { long inicio = System.currentTimeMillis(); Produto produto = interno.buscar(id); System.out.println("Busca de " + id + " levou " + (System.currentTimeMillis() - inicio) + " ms"); return produto; }}
class CacheDecorator implements ServicoProduto { private final ServicoProduto interno; private final Map<Integer, Produto> cache = new HashMap<>();
public CacheDecorator(ServicoProduto interno) { this.interno = interno; }
@Override public Produto buscar(int id) { if (cache.containsKey(id)) { System.out.println("Cache hit para " + id); return cache.get(id); } Produto produto = interno.buscar(id); cache.put(id, produto); return produto; }}
public class Loja { public static void main(String[] args) { ServicoProduto servico = new ServicoProdutoBase(); servico = new LogDecorator(servico); servico = new CacheDecorator(servico);
servico.buscar(10); // vai ao banco e guarda no cache servico.buscar(10); // cache hit }}Nesse exemplo, ServicoProdutoBase é o componente concreto, e LogDecorator e CacheDecorator são decorators. O consumidor continua usando a interface ServicoProduto; combinar log e cache é só uma questão de montar a cadeia
