Node.js – Singleton e istanze di un modulo
Come molti avranno già avuto modo di constatare, in Node.js se due file nel tuo progetto richiedono lo stesso modulo che istanzia una nuova classe, otterranno indietro lo stesso oggetto, perchè Node usa una cache per le richieste dello stesso modulo. Questo non vale per moduli uguali ma posti in percorsi differenti. Motivo per cui in Node non bisogna far molto affidamento sui Singleton.
I Moduli sono salvati in cache subito dopo la prima inclusione. Questo significa (fra le altre cose) che ogni successiva chiamata ad un ipotetico modulo require('foo')
restituirà sempre lo stesso oggetto.
Questa potrebbe essere la ragione per cui Express, popolare web framework disponibile come modulo npm, restituisce un costruttore per la creazione di app piuttosto che restituire un’app direttamente quando invocato: require('express');
. Questo forza lo sviluppatore a istanziare un oggetto ed esplicitamente passarlo ad altri moduli che potrebbero averne bisogno. Questo evita errori e lascia liberta all’utente nella gestione delle istanze.
Chiamate multiple a require('foo')
non significano necessariamente che il codice venga eseguito più volte. Questa è una nota importante.
Se si vuole avere un modulo che venga eseguito più volte, è necessario esportare una funzione e chiamare quella funzione.
Mostriamo un paio d’esempi.
Esempi
Consideriamo questi due file.
– Il file B.js contiene un’ipotetica classe:
// file B.js function classe(){ this.helloMex = 'Ciao!'; this.byeMex = 'Addio!'; }; classe.prototype.sayHello = function(first_argument) { console.log(this.helloMex); return this; // chainable method }; classe.prototype.sayBye = function(first_argument) { console.log(this.byeMex); return this; }; classe.prototype.setBye = function(first_argument) { this.byeMex = first_argument; return this; }; module.exports = new classe(); |
Il comportamento che ci aspettiamo è che ad ogni require('./B.js')
venga restituita una nuova istanza della classe.
In realtà vedremo che non è così.
– Il file A.js è così definito:
// file A.js require('./B.js') .sayHello() .setBye('Scappo!') .sayBye(); require('./B.js') .sayHello() .sayBye(); |
In questo file includiamo 2 volte lo script B.js e in catena richiamiamo i suoi metodi.
Sulla prima inclusione, dunque sulla prima istanza, andiamo a modificare il “saluto” di default con la parola “Scappo!”.
Sulla seconda istanza invece lasciamo tutto invariato.
Il risultato che ci aspetteremmo:
Ciao! Scappo! Ciao! Addio! |
Ciò che realmente visualizziamo invece:
Ciao! Scappo! Ciao! Scappo! |
Questo a dimostrazione di come inclusioni multiple dello stesso modulo vengano cachate da Node. Includendo più volte B.js l’istanza dell’oggetto restituito sarà sempre una.
Come fare dunque a generare nuove istanze includendo il modulo? semplicemente restituendo una funzione da chiamare e che restituisca una nuova istanza. In questo modo:
// file B.js function classe() { this.helloMex = 'Ciao!'; this.byeMex = 'Addio!'; }; classe.prototype.sayHello = function(first_argument) { console.log(this.helloMex); return this; // chainable method }; classe.prototype.sayBye = function(first_argument) { console.log(this.byeMex); return this; }; classe.prototype.setBye = function(first_argument) { this.byeMex = first_argument; return this; }; module.exports = function() { return new classe(); } |
E che poi chiamiamo in questo modo:
// file A.js require('./B')() .sayHello() .setBye('Scappo!') .sayBye(); require('./B')() .sayHello() .sayBye(); |
Notare le parentesi subito dopo il require. In questo modo subito dopo l’inclusione eseguiamo la funzione che istanzia un nuovo oggetto della classe e lo restituisce.
Il risultato attesto a questo punto è proprio:
Ciao! Scappo! Ciao! Addio! |
Sperando di aver chiarito questo importante aspetto di Node, vi rimando ai commenti giù per qualsiasi chiarimento.
Commenti