Decorator: adicionando comportamento sem herdar nada
Como empilhar funcionalidades num objeto sem criar uma subclasse para cada combinação possível
Um serviço de leitura de arquivos começa simples: lê o conteúdo e devolve uma String. Com o tempo, surge a necessidade de comprimir o conteúdo antes de salvar, depois criptografar, depois talvez adicionar um cache. A tentação mais comum é resolver isso com herança:
public class LeitorArquivo { /* ... */ }
public class LeitorArquivoComCompressao extends LeitorArquivo { /* ... */ }
public class LeitorArquivoComCriptografia extends LeitorArquivo { /* ... */ }
public class LeitorArquivoComCompressaoECriptografia extends LeitorArquivoComCompressao { /* ... */ }
O problema fica óbvio na quarta classe: cada combinação nova de funcionalidades exige uma subclasse nova, e o número de combinações cresce de forma combinatória. Compressão, criptografia e cache juntos já seriam três classes a mais, e cada uma delas fixa a ordem em que as funcionalidades são aplicadas.
Envolver em vez de herdar
O Decorator resolve isso trocando herança por composição. Em vez de estender a classe original, cria-se um “envoltório” que implementa a mesma interface e delega para o objeto original, adicionando seu próprio comportamento antes ou depois da chamada.
public interface LeitorArquivo {
String ler(String caminho);
}
public class LeitorArquivoSimples implements LeitorArquivo {
@Override
public String ler(String caminho) {
// lê o arquivo do disco
return Files.readString(Path.of(caminho));
}
}
Cada funcionalidade extra vira um decorador, que recebe outro LeitorArquivo no construtor e delega para ele:
public abstract class LeitorArquivoDecorator implements LeitorArquivo {
protected final LeitorArquivo leitor;
protected LeitorArquivoDecorator(LeitorArquivo leitor) {
this.leitor = leitor;
}
}
public class LeitorComDescompressao extends LeitorArquivoDecorator {
public LeitorComDescompressao(LeitorArquivo leitor) {
super(leitor);
}
@Override
public String ler(String caminho) {
String conteudo = leitor.ler(caminho);
return descomprimir(conteudo);
}
}
public class LeitorComDescriptografia extends LeitorArquivoDecorator {
public LeitorComDescriptografia(LeitorArquivo leitor) {
super(leitor);
}
@Override
public String ler(String caminho) {
String conteudo = leitor.ler(caminho);
return descriptografar(conteudo);
}
}
As funcionalidades se combinam empilhando decoradores em tempo de execução, não em tempo de compilação:
LeitorArquivo leitor = new LeitorComDescriptografia(
new LeitorComDescompressao(
new LeitorArquivoSimples()
)
);
String conteudo = leitor.ler("relatorio.dat");
Quem chama leitor.ler(...) não precisa saber quantas camadas existem por trás, só enxerga a interface LeitorArquivo. E a ordem das camadas pode mudar conforme a necessidade (descriptografar antes de descomprimir, ou o contrário) sem criar nenhuma classe nova, só reorganizando a montagem.
Um exemplo que já existe na própria linguagem
Quem já usou java.io esbarrou no Decorator sem perceber:
InputStream entrada = new BufferedInputStream(
new GZIPInputStream(
new FileInputStream("dados.gz")
)
);
FileInputStream lê bytes do disco, GZIPInputStream descomprime o que vier do stream que ele envolve, e BufferedInputStream adiciona um buffer por cima de qualquer InputStream. Nenhuma dessas classes sabe da existência das outras, elas só conhecem a interface InputStream comum, exatamente o mesmo princípio do exemplo do leitor de arquivos.
Quando vale a pena
O Decorator compensa quando funcionalidades precisam ser combinadas de formas variadas e a lista de combinações tende a crescer, porque é justamente aí que a herança simples explode em número de subclasses. Para um caso com uma ou duas variações fixas que nunca mudam, uma subclasse direta ou até um if simples já resolvem sem a indireção extra de empilhar objetos. Vale notar também que cada decorador faz o objeto ficar com um tipo concreto diferente, o que pode dificultar depuração se a pilha de decoradores crescer demais sem necessidade real.
Fonte: Refactoring Guru.
Um grande abraço e até o próximo post!