Decorator
Anexa responsabilidades adicionais a um objeto dinamicamente, oferecendo uma alternativa flexível à subclassificação para estender funcionalidade.
Intenção
Envolver um objeto dentro de outro objeto que adiciona novo comportamento antes ou depois de delegar ao original, permitindo compor responsabilidades em tempo de execução em vez de fixá-las em uma hierarquia de classes rígida.
Problema
Você precisa adicionar comportamentos opcionais -- como logging, criptografia, compressão, ou cache -- a objetos individuais em tempo de execução. Usar herança para criar cada combinação possível levaria a uma explosão de classes (ex: LoggingEncryptedCompressedStream), e modificar a classe original viola o Princípio Aberto/Fechado.
Solução
Defina uma interface comum tanto para o componente principal quanto para seus decoradores. Cada decorador implementa a interface e mantém uma referência a um componente envolvido. Ele delega o trabalho principal ao objeto envolvido e adiciona seu próprio comportamento antes ou depois da delegação. Decoradores podem ser empilhados arbitrariamente, compondo comportamentos em tempo de execução.
Participantes
- Component -- a interface comum tanto para objetos concretos quanto para decoradores
- ConcreteComponent -- o objeto original que pode ser decorado
- Decorator -- classe base ou interface que envolve um Component e é compatível com sua interface
- ConcreteDecorator -- adiciona comportamento específico antes ou depois de delegar ao Component envolvido
Vantagens
- Mais flexível que herança estática -- comportamentos podem ser adicionados e removidos em tempo de execução
- Evita classes sobrecarregadas de funcionalidades no topo da hierarquia (problema da explosão de classes)
- Segue o Princípio da Responsabilidade Única -- cada decorador cuida de uma preocupação
- Decoradores podem ser compostos em qualquer ordem para criar combinações customizadas
Desvantagens
- Muitos objetos pequenos podem tornar o sistema mais difícil de entender e depurar
- Remover um decorador específico do meio de uma cadeia é difícil
- A ordem dos decoradores pode importar, e uma ordem incorreta leva a bugs sutis
- A identidade do componente muda -- obj !== decoratedObj, o que pode quebrar checagens de igualdade
Analogia do mundo real
Vestir camadas de roupa é o padrão Decorator. Você começa com uma camada base (camiseta), depois adiciona um suéter para aquecer, depois uma capa de chuva para impermeabilizar. Cada camada adiciona uma responsabilidade sem alterar as camadas de baixo, e você pode combinar dependendo das condições.
Casos de uso
- Adicionar logging, medição de tempo, ou lógica de retry a chamadas de serviço sem modificar o serviço
- Compor comportamentos de streams de I/O: buffering, compressão, criptografia
- Adicionar regras de validação dinamicamente a campos de formulário em um framework de UI
- Envolver um repositório de dados com funcionalidade de cache ou trilha de auditoria
- Adicionar middleware de autenticação ou rate-limiting a handlers HTTP
Exemplos de código
interface DataSource {
void write(String data);
String read();
}
final class FileDataSource implements DataSource {
private String content = "";
@Override
public void write(String data) {
this.content = data;
System.out.println("Wrote raw data to file");
}
@Override
public String read() {
return content;
}
}
abstract class DataSourceDecorator implements DataSource {
protected final DataSource wrapped;
protected DataSourceDecorator(DataSource wrapped) {
this.wrapped = wrapped;
}
@Override
public void write(String data) {
wrapped.write(data);
}
@Override
public String read() {
return wrapped.read();
}
}
final class EncryptionDecorator extends DataSourceDecorator {
EncryptionDecorator(DataSource wrapped) {
super(wrapped);
}
@Override
public void write(String data) {
System.out.println("Encrypting data");
super.write("ENC[" + data + "]");
}
@Override
public String read() {
String raw = super.read();
System.out.println("Decrypting data");
return raw.replace("ENC[", "").replace("]", "");
}
}
final class LoggingDecorator extends DataSourceDecorator {
LoggingDecorator(DataSource wrapped) {
super(wrapped);
}
@Override
public void write(String data) {
System.out.println("[log] writing " + data.length() + " chars");
super.write(data);
}
}
public class DecoratorDemo {
public static void main(String[] args) {
// Layer decorators: logging wraps encryption wraps the raw file source
DataSource source = new LoggingDecorator(new EncryptionDecorator(new FileDataSource()));
source.write("top secret");
System.out.println(source.read()); // top secret
}
}Decoradores que adicionam logging e criptografia a uma interface DataSource, demonstrando como comportamentos se compõem envolvendo um decorador dentro de outro.
Padrões relacionados
Adapter
Converte a interface de uma classe em outra interface que os clientes esperam, permitindo que classes trabalhem juntas mesmo com interfaces incompatíveis.
Proxy
Fornece um substituto ou placeholder para outro objeto para controlar o acesso a ele.