Os Decorators representam uma proposta de sintaxe para o JavaScript (ES6+) que permite anotar e modificar o comportamento de classes e seus membros (métodos e propriedades) de maneira declarativa. Na prática, um decorator nada mais é do que uma função pura que recebe o alvo da modificação como argumento e retorna esse alvo modificado ou um novo descritor.
Escopo de Aplicação
Diferente de outras funcionalidades, os decorators possuem restrições sobre onde podem ser aplicados:
- Classes: Podem modificar a definição global da classe.
- Membros de Classe: Incluindo métodos, propriedades, acessoers (get/set).
- Funções Simples: Não podem ser decoradas devido ao hoisting (içamento) de funções no JavaScript.
Decorators de Classe
Quando aplicado a uma classe, o decorator recebe como único argumento o construtor da classe que está sendo decorada.
function registrarData(target) {
target.criadoEm = new Date().toISOString();
}
@registrarData
class Usuario {
// Lógica da classe
}
console.log(Usuario.criadoEm); // Exibe a data de criação vinculada à classe
Caso seja necessário passar parâmetros para o decorator, utiliza-se o padrão Factory, onde uma função externa envolve a lógica do decorator e retorna a função que será executada pelo motor do JavaScript.
function definirVersao(identificador) {
return function(construtor) {
construtor.prototype.versaoApp = identificador;
};
}
@definirVersao('2.1.0')
class Aplicacao {
executar() {
console.log(`Rodando versão ${this.versaoApp}`);
}
}
const app = new Aplicacao();
app.executar(); // "Rodando versão 2.1.0"
Note que as alterações realizadas por um decorator ocorrem no momento da compilação (ou transpilação), e não em tempo de execução para cada instância, o que torna o processo altamente performático.
Decorators de Métodos
A decoração de métodos permite interceptar chamadas, modificar permissões ou realizar logs. Neste caso, a função recebe três parâmetros:
- target: O protótipo da classe.
- key: O nome do membro que está sendo decorado.
- descriptor: O objeto de descrição da propriedade (contendo configurações como
writable,enumerable, etc).
function bloqueado(target, nome, descriptor) {
// Impede que o método seja sobrescrito
descriptor.writable = false;
return descriptor;
}
class Servico {
@bloqueado
conectar() {
console.log("Conexão estabelecida.");
}
}
const srv = new Servico();
// Tentativa de alterar o método resultará em erro ou será ignorada
srv.conectar = () => console.log("Hackeado!");
srv.conectar(); // "Conexão estabelecida."
Interceptação de Lógica
Um uso comum é envolver a lógica original do método para adicionar funcionalidades transversais, como telemetria ou validação.
function monitorarDesempenho(target, chave, descritor) {
const metodoOriginal = descritor.value;
descritor.value = function(...args) {
console.time(chave);
const retorno = metodoOriginal.apply(this, args);
console.timeEnd(chave);
return retorno;
};
return descritor;
}
class Processador {
@monitorarDesempenho
executarTarefaComplexa() {
// Simulação de processamento
for(let i = 0; i < 1000000; i++) {}
return "Finalizado";
}
}
const proc = new Processador();
proc.executarTarefaComplexa(); // Exibe o tempo gasto no console
Os decorators facilitam a separação de preocupações (Separation of Conecrns), permitindo que regras de negócio permaneçam limpas enquanto comportamentos genéricos são abstraídos para funções utilitárias reutilizáveis.