Pular para o conteúdo

Decorator

Suponha variações combináveis de um comportamento: um relatório pode ser comprimido, assinado digitalmente, com marca-d’água — em qualquer combinação. Por herança, cada combinação vira uma subclasse (RelatorioComprimidoAssinado, RelatorioAssinadoComMarca…): 3 variações já geram 7 classes, e a escolha é fixada em tempo de compilação.

Uma interface comum, um componente concreto e decorators que a implementam envolvendo outro componente:

public interface GeradorRelatorio {
byte[] gerar(DadosRelatorio dados);
}
public class GeradorPdf implements GeradorRelatorio {
@Override
public byte[] gerar(DadosRelatorio dados) {
return renderizarPdf(dados);
}
}
public abstract class RelatorioDecorator implements GeradorRelatorio {
protected final GeradorRelatorio delegado;
protected RelatorioDecorator(GeradorRelatorio delegado) {
this.delegado = delegado;
}
}
public class ComCompressao extends RelatorioDecorator {
public ComCompressao(GeradorRelatorio delegado) {
super(delegado);
}
@Override
public byte[] gerar(DadosRelatorio dados) {
return comprimir(delegado.gerar(dados));
}
}
public class ComAssinaturaDigital extends RelatorioDecorator {
public ComAssinaturaDigital(GeradorRelatorio delegado) {
super(delegado);
}
@Override
public byte[] gerar(DadosRelatorio dados) {
return assinar(delegado.gerar(dados));
}
}
GeradorRelatorio gerador =
new ComAssinaturaDigital(
new ComCompressao(
new GeradorPdf()));

O cliente vê só GeradorRelatorio; combinações novas são composição em runtime, sem nenhuma classe extra — OCP em estado puro. O exemplo canônico da plataforma é o java.io: new BufferedInputStream(new GZIPInputStream(new FileInputStream(...))).

classDiagram
    class GeradorRelatorio {
        <<interface>>
        +gerar(DadosRelatorio) byte[]
    }
    class GeradorPdf {
        +gerar(DadosRelatorio) byte[]
    }
    class RelatorioDecorator {
        <<abstract>>
        #GeradorRelatorio delegado
    }
    class ComCompressao
    class ComAssinaturaDigital

    GeradorRelatorio <|.. GeradorPdf
    GeradorRelatorio <|.. RelatorioDecorator
    RelatorioDecorator <|-- ComCompressao
    RelatorioDecorator <|-- ComAssinaturaDigital
    RelatorioDecorator o--> GeradorRelatorio : envolve

As estruturas são idênticas (mesma interface, delegação); a intenção difere. O Proxy controla o acesso a um alvo que ele conhece e geralmente instancia/esconde (lazy, cache, permissão). O Decorator acrescenta comportamento e é empilhável pelo cliente, que monta a cadeia. Regra de bolso: quem decide a composição? Cliente → Decorator; o próprio wrapper → Proxy.

  • Ordem implícita: assinar(comprimir(x))comprimir(assinar(x)) — quando a ordem dos decorators importa, documente-a ou encapsule a montagem numa factory; deixá-la ao acaso é bug esperando.
  • Decorator que muda o contrato: filtrar, alterar semântica ou lançar exceções novas viola LSP — o cliente esperava o comportamento da interface.
  • Pilha profunda ilegível: dezenas de camadas dificultam debug (stack traces gigantes); considere consolidar.
  • Usar onde Strategy bastava: se os comportamentos são alternativos (um ou outro), é Strategy; Decorator é para comportamentos cumulativos.

1. Que problema o Decorator resolve que a herança não resolve bem?

Resposta

Variações combináveis de comportamento. Por herança, cada combinação vira uma subclasse (explosão combinatória) fixada em compilação; com decorators, combinações são composição em runtime, sem classes novas.

2. Como distinguir Decorator de Proxy, já que a estrutura é a mesma?

Resposta

Pela intenção e por quem monta: o Proxy controla o acesso a um alvo que ele mesmo conhece/esconde; o Decorator acrescenta comportamento e é empilhado pelo cliente, que decide a composição.

3. Por que um decorator que filtra ou altera a semântica é perigoso?

Resposta

Porque quebra a substituibilidade (LSP): o cliente programou contra o contrato da interface, e o decorator entrega menos (ou diferente) do que o contrato promete.

4. Dê um exemplo de Decorator na biblioteca padrão do Java.

Resposta

O pacote java.io: BufferedInputStream, GZIPInputStream etc. envolvem outro InputStream acrescentando buffer, descompressão — todos com a mesma interface, empilháveis em qualquer ordem que faça sentido.