Pattern singleton in nodejs: è necessario?


95

Di recente mi sono imbattuto in questo articolo su come scrivere un singleton in Node.js. Conosco la documentazione di require stati che:

I moduli vengono memorizzati nella cache dopo il primo caricamento. Più chiamate a require('foo')potrebbero non causare l'esecuzione più volte del codice del modulo.

Quindi sembra che ogni modulo richiesto possa essere facilmente utilizzato come singleton senza il codice boilerplate singleton.

Domanda:

L'articolo sopra fornisce una soluzione per la creazione di un singleton?


1
Ecco un 5 min. spiegazione su questo argomento (scritta dopo v6 e npm3): medium.com/@lazlojuly/…
lazlojuly

Risposte:


56

Questo ha fondamentalmente a che fare con la memorizzazione nella cache di nodejs. Chiaro e semplice.

https://nodejs.org/api/modules.html#modules_caching

(v 6.3.1)

Caching

I moduli vengono memorizzati nella cache dopo il primo caricamento. Ciò significa (tra le altre cose) che ogni chiamata a require ('foo') riceverà esattamente lo stesso oggetto restituito, se risolverà nello stesso file.

Più chiamate a require ('foo') potrebbero non causare l'esecuzione più volte del codice del modulo. Questa è una caratteristica importante. Con esso, gli oggetti "parzialmente completati" possono essere restituiti, consentendo così il caricamento delle dipendenze transitive anche quando causerebbero cicli.

Se vuoi che un modulo esegua il codice più volte, esporta una funzione e chiama quella funzione.

Avvertenze sulla memorizzazione nella cache del modulo

I moduli vengono memorizzati nella cache in base al nome file risolto. Poiché i moduli possono risolversi in un nome file diverso in base alla posizione del modulo chiamante (caricamento dalle cartelle node_modules), nonèuna garanzia che require ('foo') restituirà sempre lo stesso identico oggetto, se risolverà in file diversi .

Inoltre, su file system o sistemi operativi senza distinzione tra maiuscole e minuscole, diversi nomi di file risolti possono puntare allo stesso file, ma la cache li tratterà comunque come moduli diversi e ricaricherà il file più volte. Ad esempio, require ('./ foo') e require ('./ FOO') restituiscono due oggetti diversi, indipendentemente dal fatto che ./foo e ./FOO siano o meno lo stesso file.

Quindi in termini semplici.

Se vuoi un Singleton; esportare un oggetto .

Se non vuoi un Singleton; esportare una funzione (e fare cose / restituire cose / qualunque cosa in quella funzione).

Per essere MOLTO chiari, se lo fai correttamente dovrebbe funzionare, guarda https://stackoverflow.com/a/33746703/1137669 (la risposta di Allen Luce). Spiega nel codice cosa succede quando la memorizzazione nella cache non riesce a causa di nomi di file risolti in modo diverso. Ma se risolvi SEMPRE con lo stesso nome di file dovrebbe funzionare.

Aggiornamento 2016

creare un vero singleton in node.js con simboli es6 Un'altra soluzione : in questo link

Aggiorna 2020

Questa risposta si riferisce a CommonJS (il modo in cui Node.js importa / esporta moduli). Node.js molto probabilmente passerà ai moduli ECMAScript : https://nodejs.org/api/esm.html (ECMAScript è il vero nome di JavaScript se non lo sapevi)

Durante la migrazione a ECMAScript, per ora leggi quanto segue: https://nodejs.org/api/esm.html#esm_writing_dual_packages_while_avoiding_or_minimizing_hazards


3
Se vuoi un Singleton; esporta un oggetto ... che ha aiutato grazie
danday74

1
Questa è una specie di cattiva idea - per molte ragioni fornite altrove in questa pagina - ma il concetto è essenzialmente valido, vale a dire che nelle circostanze nominali stabilite, le affermazioni in questa risposta sono vere. Se vuoi un singleton veloce e sporco, probabilmente funzionerà, ma non avviare alcuna navetta con il codice.
Adam Tolley

@AdamTolley "per molte ragioni fornite altrove in questa pagina", ti riferisci a file di collegamento simbolico o nomi di file con errori di ortografia che apparentemente non utilizzano la stessa cache? Indica nella documentazione il problema relativo ai file system o ai sistemi operativi senza distinzione tra maiuscole e minuscole. Per quanto riguarda il collegamento simbolico, puoi leggere di più qui poiché è stato discusso su github.com/nodejs/node/issues/3402 . Inoltre, se stai collegando file simbolici o non comprendi correttamente il tuo sistema operativo e il tuo nodo, non dovresti essere nemmeno vicino all'industria dell'ingegneria aerospaziale;), tuttavia capisco il tuo punto ^^.
K - La tossicità in SO sta crescendo.

@KarlMorrison - solo per il fatto che la documentazione non lo garantisce, il fatto che sembra essere un comportamento non specificato, o qualsiasi altra ragione razionale per non fidarsi di questo particolare comportamento del linguaggio. Forse la cache funziona in modo diverso in un'altra implementazione, o ti piace lavorare in REPL e sovvertire del tutto la funzionalità di memorizzazione nella cache. Il punto è che la cache è un dettaglio di implementazione e il suo utilizzo come equivalente singleton è un trucco intelligente. Adoro gli hack intelligenti, ma dovrebbero essere differenziati, tutto qui - (anche nessuno sta lanciando navette con nodo, ero sciocco)
Adam Tolley

136

Tutto quanto sopra è troppo complicato. C'è una scuola di pensiero che afferma che i modelli di progettazione mostrano carenze del linguaggio reale.

I linguaggi con OOP basato su prototipi (senza classi) non necessitano affatto di un pattern singleton. Crei semplicemente un singolo (tonnellata) oggetto al volo e poi lo usi.

Per quanto riguarda i moduli nel nodo, sì, per impostazione predefinita vengono memorizzati nella cache, ma può essere modificato, ad esempio, se si desidera il caricamento a caldo delle modifiche ai moduli.

Ma sì, se vuoi usare oggetti condivisi ovunque, metterlo in un modulo di esportazione va bene. Basta non complicarlo con il "pattern singleton", non è necessario in JavaScript.


27
È strano che nessuno ottenga voti positivi ... hanno un +1 perThere is a school of thought which says design patterns are showing deficiencies of actual language.
Esailija

64
I singleton non sono un anti-pattern.
wprl

4
@herby, sembra una definizione eccessivamente specifica (e quindi errata) del pattern singleton.
wprl

19
La documentazione dice: "Più chiamate a require ('foo') potrebbero non causare l'esecuzione più volte del codice del modulo.". Dice "potrebbe non", non dice "non lo farò", quindi chiedere come assicurarsi che l'istanza del modulo sia creata solo una volta in un'applicazione è una domanda valida dal mio punto di vista.
xorcus

9
È fuorviante che questa sia la risposta corretta a questa domanda. Come @mike ha sottolineato di seguito, è possibile che un modulo venga caricato più di una volta e tu abbia due istanze. Sto riscontrando il problema in cui ho solo una copia di Knockout ma vengono create due istanze perché il modulo viene caricato due volte.
dgaviola

26

No. Quando il caching del modulo di Node fallisce, il pattern singleton fallisce. Ho modificato l'esempio per eseguire in modo significativo su OSX:

var sg = require("./singleton.js");
var sg2 = require("./singleton.js");
sg.add(1, "test");
sg2.add(2, "test2");

console.log(sg.getSocketList(), sg2.getSocketList());

Questo fornisce l'output anticipato dall'autore:

{ '1': 'test', '2': 'test2' } { '1': 'test', '2': 'test2' }

Ma una piccola modifica sconfigge la memorizzazione nella cache. Su OSX, fai questo:

var sg = require("./singleton.js");
var sg2 = require("./SINGLETON.js");
sg.add(1, "test");
sg2.add(2, "test2");

console.log(sg.getSocketList(), sg2.getSocketList());

Oppure, su Linux:

% ln singleton.js singleton2.js

Quindi modificare la sg2riga di richiesta in:

var sg2 = require("./singleton2.js");

E bam , il singleton è sconfitto:

{ '1': 'test' } { '2': 'test2' }

Non conosco un modo accettabile per aggirare questo problema. Se senti davvero il bisogno di creare qualcosa di simile al singleton e sei d'accordo con l'inquinamento dello spazio dei nomi globale (e dei molti problemi che possono derivare), puoi modificare le righe getInstance()e l'autore exportsin:

singleton.getInstance = function(){
  if(global.singleton_instance === undefined)
    global.singleton_instance = new singleton();
  return global.singleton_instance;
}

module.exports = singleton.getInstance();

Detto questo, non mi sono mai imbattuto in una situazione su un sistema di produzione in cui avevo bisogno di fare qualcosa del genere. Inoltre, non ho mai sentito la necessità di utilizzare il pattern singleton in Javascript.


21

Esaminando un po 'più in là le avvertenze sulla memorizzazione nella cache dei moduli nella documentazione dei moduli:

I moduli vengono memorizzati nella cache in base al nome file risolto. Poiché i moduli possono risolversi in un nome file diverso in base alla posizione del modulo chiamante (caricamento dalle cartelle node_modules), nonèuna garanzia che require ('foo') restituirà sempre lo stesso identico oggetto, se risolverà in file diversi .

Quindi, a seconda di dove ti trovi quando richiedi un modulo, è possibile ottenere un'istanza diversa del modulo.

Sembra che i moduli non siano una soluzione semplice per creare singleton.

Modifica: o forse lo sono . Come @mkoryak, non riesco a trovare un caso in cui un singolo file potrebbe risolversi in nomi di file diversi (senza utilizzare collegamenti simbolici). Ma (come commenta @JohnnyHK), più copie di un file in node_modulesdirectory diverse verranno caricate e archiviate separatamente.


ok, l'ho letto 3 volte e non riesco ancora a pensare a un esempio in cui si risolverà in un nome file diverso. Aiuto?
mkoryak

1
@mkoryak Penso che si riferisca a casi in cui hai due diversi moduli che stai richiedendo da node_modulesdove ognuno dipende dallo stesso modulo, ma ci sono copie separate di quel modulo dipendente nella node_modulessottodirectory di ciascuno dei due diversi moduli.
JohnnyHK

@mike sei proprio qui che il modulo viene istanziato più volte se referenziato attraverso percorsi diversi. Ho colpito il caso durante la scrittura di unit test per i moduli del server. Ho bisogno di una specie di istanza singleton. come ottenerlo?
Sushil

Un esempio potrebbe essere i percorsi relativi. per esempio. Dato che require('./db')è in due file separati, il codice per il dbmodulo viene eseguito due volte
scritto il

9
Ho appena avuto un brutto bug poiché il sistema del modulo del nodo non è sensibile al maiuscolo / minuscolo. ho chiamato require('../lib/myModule.js');in un file e require('../lib/mymodule.js');in un altro e non ha consegnato lo stesso oggetto.
heyarne

18

Un singleton in node.js (o nel browser JS, se è per questo) come quello è completamente inutile.

Poiché i moduli sono memorizzati nella cache e con stato, l'esempio fornito sul collegamento fornito potrebbe essere facilmente riscritto in modo molto più semplice:

var socketList = {};

exports.add = function (userId, socket) {
    if (!socketList[userId]) {
        socketList[userId] = socket;
    }
};

exports.remove = function (userId) {
    delete socketList[userId];
};

exports.getSocketList = function () {
    return socketList;
};
// or
// exports.socketList = socketList

5
I documenti dicono che " potrebbe non causare l'esecuzione più volte del codice del modulo", quindi è possibile che venga chiamato più volte, e se questo codice viene eseguito di nuovo, socketList verrà reimpostato su un elenco vuoto
Jonathan.

3
@Jonathan. Il contesto nei documenti attorno a quella citazione sembra rappresentare un caso abbastanza convincente che potrebbe non esserlo essere utilizzato in uno stile RFC NON DEVE .
Michael

6
@Michael "may" è una parola divertente come questa. Voglia di avere una parola che quando negata significa "forse no" o "decisamente no" ..
OJFord

1
Si may notapplica quando si utilizzano npm linkaltri moduli durante lo sviluppo. Quindi fai attenzione quando usi moduli che si basano su una singola istanza come un eventBus.
mediafreakch

10

L'unica risposta qui che utilizza le classi ES6

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

richiedi questo singleton con:

const summary = require('./SummaryModule')
summary.init(true)
summary.anotherMethod()

L'unico problema qui è che non puoi passare i parametri al costruttore della classe, ma ciò può essere aggirato chiamando manualmente un initmetodo.


la domanda è "sono necessari singoli", non "come si scrive"
mkoryak

@ danday74 Come possiamo usare la stessa istanza summaryin un'altra classe, senza inizializzarla di nuovo?
M Faisal Hameed

1
In Node.js è sufficiente richiederlo in un altro file ... const summary = require ('./ SummaryModule') ... e sarà la stessa istanza. Puoi verificarlo creando una variabile membro e impostando il suo valore in un file che lo richiede e quindi ottenendo il suo valore in un altro file che lo richiede. Dovrebbe essere il valore impostato.
danday74

9

Non hai bisogno di niente di speciale per fare un singleton in js, il codice nell'articolo potrebbe anche essere:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

Al di fuori di node.js (ad esempio, nel browser js), è necessario aggiungere manualmente la funzione wrapper (viene eseguita automaticamente in node.js):

var singleton = function() {
    var socketList = {};
    return {
        add: function() {},
        ...
    };
}();

Come sottolineato da @Allen Luce, se il caching del nodo fallisce, fallisce anche il pattern singleton.
rakeen

6

I singleton vanno bene in JS, semplicemente non hanno bisogno di essere così prolissi.

In node se hai bisogno di un singleton, ad esempio per utilizzare la stessa istanza ORM / DB su vari file nel tuo livello server, puoi inserire il riferimento in una variabile globale.

Basta scrivere un modulo che crea la var globale se non esiste, quindi restituisce un riferimento a quella.

@ allen-luce aveva ragione con il suo esempio di codice della nota a piè di pagina copiato qui:

singleton.getInstance = function(){
  if(global.singleton_instance === undefined)
    global.singleton_instance = new singleton();
  return global.singleton_instance;
};

module.exports = singleton.getInstance();

ma è importante notare che l'utilizzo della newparola chiave non lo è obbligatorio. Qualsiasi vecchio oggetto, funzione, iife, ecc. Funzionerà - non c'è nessun vudù OOP che sta accadendo qui.

punti bonus se chiudi un qualche oggetto all'interno di una funzione che restituisce un riferimento ad essa e rendi quella funzione globale - allora anche la riassegnazione della variabile globale non distruggerà le istanze già create da essa - sebbene questo sia discutibilmente utile.


non hai bisogno di niente di tutto ciò. puoi farlo semplicemente module.exports = new Foo()perché module.exports non verrà eseguito di nuovo, a meno che tu non faccia qualcosa di veramente stupido
mkoryak

NON dovresti assolutamente fare affidamento sugli effetti collaterali dell'implementazione. Se hai bisogno di una singola istanza, collegala a una globale, nel caso in cui l'implementazione cambi.
Adam Tolley

La risposta di cui sopra era anche un malinteso della domanda originale come "Dovrei usare i singleton in JS o il linguaggio li rende non necessari?", Che sembra essere un problema anche con molte altre risposte. Sono d'accordo con la mia raccomandazione contro l'utilizzo dell'implementazione require in sostituzione di un'implementazione singleton appropriata ed esplicita.
Adam Tolley

1

Mantenerlo semplice.

foo.js

function foo() {

  bar: {
    doSomething: function(arg, callback) {
      return callback('Echo ' + arg);
    };
  }

  return bar;
};

module.exports = foo();

Allora solo

var foo = require(__dirname + 'foo');
foo.doSomething('Hello', function(result){ console.log(result); });

la domanda è "sono necessari singoli", non "come si scrive"
mkoryak
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.