Decoratori in JavaScript

I Decoratori (JavaScript)

I decoratori in JavaScript sono una funzionalità sperimentale che permette di modificare il comportamento di classi, metodi, proprietà e parametri in modo dichiarativo. Introdotti nelle proposte di ECMAScript, i decoratori forniscono un modo elegante per applicare funzionalità aggiuntive senza modificare direttamente il codice della classe o della funzione. Sono particolarmente utili nei framework moderni come Angular e NestJS, che li utilizzano per definire componenti, servizi e middleware.

Ad esempio in NestJS è frequente trovare codice di questo tipo:

import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

In questo caso i decoratori son utilizzati per aggiungere caratteristiche e funzionalità ad una classe e renderla un controller e a definire una rotta che in questo caso sarà utilizzata per chiamate HTTP di tipo GET.

Come Funzionano i Decoratori

I decoratori operano come funzioni che vengono applicate ad una classe o a un elemento della classe (ad esempio, un metodo) e possono modificarne il comportamento. Si basano sulla riflessione e sulla metaprogrammazione, consentendo di aggiungere validazioni, logiche di caching, gestione degli errori o altre funzionalità senza toccare il codice originale. Poiché sono ancora in fase di standardizzazione, per utilizzarli nei progetti è spesso necessario abilitare specifiche opzioni nel compilatore TypeScript

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}

o usare strumenti come Babel.

Scenari d’Uso dei Decoratori

I decoratori trovano applicazione in diversi contesti:

  • Logging e Monitoraggio Possono essere usati per registrare chiamate ai metodi, raccogliere metriche o tracciare l’esecuzione del codice senza modificarne il contenuto.
  • Validazione degli Input Applicabili su metodi per garantire che gli argomenti rispettino determinate regole prima dell’esecuzione della funzione.
  • Autenticazione e Autorizzazione Utili per controllare gli accessi a metodi e proprietà, ad esempio verificando il ruolo dell’utente.
  • Gestione degli Errori Possono intercettare eccezioni e applicare strategie di retry o logging automatico.
  • Lazy Loading e Ottimizzazione Possono migliorare le prestazioni ritardando l’esecuzione di metodi o implementando meccanismi di caching.

Esempio di Decoratore

Vediamo un esempio semplice di come creare e utilizzare un decoratore per una classe JavaScript. Trovate poi in fondo all’articolo i link per approfondire.

Creazione di un Decoratore

Un decoratore è una funzione che prende come argomento la funzione o la classe da decorare e restituisce una nuova funzione o classe con funzionalità aggiuntive. In questo esempio, creeremo un decoratore che logga gli argomenti con cui viene chiamato un metodo.

function logTheCall(target, name, descriptor) {
  const original = descriptor.value;

  descriptor.value = function(...args) {
    console.log(`Hai chiamato ${name}, argomenti: ${args}`);
    return original.apply(this, args);
  };

  return descriptor;
}

Utilizzo del Decoratore

Ora possiamo utilizzare il decoratore logTheCall per decorare un metodo di una classe. Questo ci permetterà di vedere nel log della console gli argomenti con cui viene chiamato il metodo.

class Esempio {
  @logTheCall // <--- aggiungo il decoratore in questo modo, con "@" come prefisso
  metodoDiEsempio(arg1, arg2) {
    console.log('Eseguo il metodo');

    return arg1 + arg2;
  }
}

const classeEsempio = new Esempio();
classeEsempio.metodoDiEsempio(1, 2);

Quando chiamiamo metodoDiEsempio, vedremo nel console log:

Hai chiamato metodoDiEsempio, argomenti: 1,2
Eseguo il metodo

Ovvero prima di eseguire la fuzione vera e propria metodoDiEsempio grazie al decoratore avverrà il log a console della chiamata.

Decoratori di Classe

I decoratori possono essere utilizzati anche per le classi. Vediamo un esempio di come sigillare una classe utilizzando un decoratore.

Creazione di un Decoratore di Classe

Il seguente decoratore sigilla una classe, impedendo che vengano aggiunte nuove proprietà o metodi.

function sealed(constructor) {
  Object.seal(constructor);

  return constructor
}

@sealed
class ClasseEsempio {
  constructor(name) {
    this.name = name;
  }
}

const istanza = new ClasseEsempio('hello');

console.log(Object.isSealed(ClasseEsempio)); // true

Testare i Decoratori

Per testare i decoratori, è importante adottare strategie come il testing unitario con framework come Jest o Mocha. Il test deve verificare che il decoratore modifichi correttamente il comportamento dell’elemento su cui è applicato, senza introdurre effetti collaterali indesiderati. Ad esempio, si può verificare se un decoratore di logging registra effettivamente le chiamate ai metodi, oppure se un decoratore di validazione impedisce valori errati. Un buon approccio consiste nell’isolare il decoratore e testarlo separatamente, simulando vari scenari.

Usando Jest possiamo ad esempio testare il nostro decoratore in questo modo

describe('logTheCall Decorator', () => {
  it('dovrebbe loggare la chiamata al metodo con i suoi argomenti', () => {
    console.log = jest.fn();

    const instance = new Esempio();
    const result = instance.metodoDiEsempio(1, 2);

    expect(console.log).toHaveBeenCalledWith('Hai chiamato metodoDiEsempio, argomenti: 1,2');
    expect(result).toBe(3);
  });
});

Conclusione

I decoratori sono una potente funzionalità di JavaScript che permette di estendere il comportamento di classi e metodi in modo modulare e riutilizzabile. Sono particolarmente utili in contesti di sviluppo avanzato e possono migliorare significativamente la manutenibilità del codice.

Buon coding! 😃