Décorateurs de Méthode & Paramètres

Un décorateur de méthode permet d'en modifier le comportement par "wrapping".

Le décorateur en exemple ci-dessous n'opère aucun changement.

const Noop = () => (target, key: string) => {
return target[key];
};
class Calculator {
@Noop()
sum(a, b) {
console.log('computing...');
return a + b;
}
}
const calculator = new Calculator();

Remarquez le pattern de "currying" très fréquent dans l'implémentation de décorateurs.

La syntaxe suivante :

const Noop = () => {
return (target, key) => {
return target[key];
};
}

... est identique à celle-ci :

const Noop = () => (target, key) => {
return target[key];
};

A consommer avec modération.

Les décorateurs de paramètres permettent principalement d'ajouter des metadata à la classe pour que les décorateurs de méthode puissent s'en servir.

Mémorisation des résultats

Le décorateur ci-dessous construit progressivement un objet de mémorisation permettant de "mapper" les paramètres au dernier résultat obtenu afin d'éviter de refaire le même calcul inutilement.

const Memoize = () => (target, key: string) => {
const memory = {};
const original = target[key];
target[key] = function (...args) {
/* Retrieve last returned value from memory if available. */
const value = memory[args.toString()];
if (value !== undefined) {
return value;
}
const result = original.apply(this, args);
memory[args.toString()] = result;
return result;
};
};
class Calculator {
@Memoize()
sum(a, b) {
console.log('computing...');
return a + b;
}
}
const calculator = new Calculator();
console.log(calculator.sum(1, 2));
// computing...
// 3
console.log(calculator.sum(1, 2));
// 3
console.log(calculator.sum(1, 2));
// 3
console.log(calculator.sum(2, 2));
// computing...
// 4
console.log(calculator.sum(1, 2));
// 3

Contract checking en runtime

import 'reflect-metadata';
const _contractDictMetadataKey = '__contractDict';
const ApplyContracts = () => (target, key: string) => {
const originalMethod = target[key];
const contractDict = Reflect.getOwnMetadata(_contractDictMetadataKey, target, key);
target[key] = function (...argList) {
if (contractDict !== undefined) {
argList.forEach((value, index) => {
const contract = contractDict[index];
if (contract === undefined) {
return;
}
if (!contract(value)) {
throw new Error(`Value '${value}' does not respect contract for \`${target.constructor.name}.${key}(param_${index})\`.`);
}
});
}
return originalMethod(...argList);
}
};
const Contract = (contract: (value: any) => boolean) => (target, key, index): any => {
let contractDict = Reflect.getOwnMetadata(_contractDictMetadataKey, target, key);
if (contractDict === undefined) {
contractDict = {};
}
contractDict[index] = contract;
Reflect.defineMetadata(_contractDictMetadataKey, contractDict, target, key);
};
const isPositive = value => value > 0;
class Utils {
@ApplyContracts()
double(@Contract(isPositive) number: number): number {
return number * 2;
}
}
console.log(new Utils().double(2)); // 4
// Error: Value '0' does not respect contract for `Utils.double(param_0)`.
console.log(new Utils().double(0));