web-dev-qa-db-fra.com

Motif singleton dans nodejs - est-ce nécessaire?

Je suis récemment tombé sur cet article sur la façon d'écrire un singleton dans Node.js. Je connais la documentation de requireétats que:

Les modules sont mis en cache après leur premier chargement. Plusieurs appels à require('foo') peuvent ne pas entraîner l'exécution du code de module à plusieurs reprises.

Il semble donc que chaque module requis peut être facilement utilisé en tant que singleton sans le singleton boilerplate-code.

Question:

L'article ci-dessus fournit-il une solution de rechange à la création d'un singleton?

78
mkoryak

Cela concerne essentiellement la mise en cache de nodejs. Clair et simple.

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

(v 6.3.1)

Caching

Les modules sont mis en cache après leur premier chargement. Ça signifie (entre autres) que chaque appel à demander ('foo') obtiendra exactement le même objet est retourné, s'il se résolvait au même fichier.

Plusieurs appels à demander ('foo') peuvent ne pas provoquer que le code du module soit exécuté plusieurs fois. C'est une caractéristique importante. Avec ça, les objets "partiellement terminés" peuvent être retournés, permettant ainsi un transitif les dépendances à charger même lorsqu'elles provoqueraient des cycles.

Si vous souhaitez qu'un module exécute le code plusieurs fois, exportez-le une fonction et appelez cette fonction.

Mises en garde sur la mise en cache des modules

Les modules sont mis en cache en fonction de leur nom de fichier résolu. Depuis les modules peuvent résoudre en un nom de fichier différent en fonction de l'emplacement de l'appelant module (chargement à partir des dossiers node_modules), il n’est pas garanti que require ('foo') retournera toujours exactement le même objet, s'il le ferait résoudre à différents fichiers.

De plus, sur les systèmes de fichiers ou les systèmes d’exploitation insensibles à la casse, Différents noms de fichiers résolus peuvent désigner le même fichier, mais le cache continuera à les traiter comme des modules différents et rechargera le fichier plusieurs fois. Par exemple, require ('./ foo') et require ('./ FOO') retourne deux objets différents, que ce soit ou non ./foo et ./FOO sont le même fichier.

Donc, en termes simples.

Si vous voulez un singleton; exporter un objet.

Si vous ne voulez pas d'un Singleton; exporte une fonction (et fait des choses/retourne des choses/quoi que ce soit dans cette fonction).

Pour être très clair, si vous le faites correctement, cela devrait fonctionner, regardez https://stackoverflow.com/a/33746703/1137669 (la réponse d'Allen Luce). Elle explique en code ce qui se passe lors de la mise en cache échoue à cause de noms de fichiers résolus différemment, mais si vous résolvez TOUJOURS avec le même nom de fichier, cela devrait fonctionner.

31
Karl Morrison

Tout ce qui précède est trop compliqué. Il existe une école de pensée qui dit que les modèles de conception montrent des déficiences du langage réel.

Les langues avec prototype OOP (sans classe) n'ont pas besoin d'un motif singleton. Vous créez simplement un seul objet (tonne) à la volée, puis vous l'utilisez.

En ce qui concerne les modules du noeud, ils sont mis en cache par défaut, mais vous pouvez les modifier par exemple si vous souhaitez charger à chaud les modifications de modules.

Mais oui, si vous souhaitez utiliser un objet partagé dans son intégralité, le placer dans un module d’exportations convient parfaitement. Il suffit de ne pas le compliquer avec "motif singleton", pas besoin de le faire en JavaScript.

129
user1046334

Non. Lorsque la mise en cache du module du nœud échoue, le motif du singleton échoue. J'ai modifié l'exemple pour qu'il fonctionne de manière significative sur 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());

Cela donne le résultat attendu par l'auteur:

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

Mais une petite modification annule la mise en cache. Sur OSX, procédez comme suit:

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

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

Ou, sous Linux:

% ln singleton.js singleton2.js

Puis changez la ligne sg2 require en:

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

Et bam, le singleton est vaincu:

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

Je ne connais pas de moyen acceptable de contourner cela. Si vous sentez vraiment le besoin de créer quelque chose qui ressemble à un singleton et que vous acceptez de polluer l'espace de noms global (et les nombreux problèmes pouvant en résulter), vous pouvez modifier les lignes getInstance() et exports de l'auteur en:

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

module.exports = singleton.getInstance();

Cela dit, je ne me suis jamais retrouvé dans une situation de système de production où je devais faire quelque chose comme ça. De plus, je n'ai jamais ressenti le besoin d'utiliser le motif singleton en Javascript.

19
Allen Luce

En regardant un peu plus loin/ Avertissement de mise en cache de modules dans la documentation des modules:

Les modules sont mis en cache en fonction de leur nom de fichier résolu. Étant donné que les modules peuvent résoudre un nom de fichier différent en fonction de l'emplacement du module appelant (chargement à partir des dossiers node_modules), ce n'est pas une garantie qui nécessite ('foo') retournera toujours le même objet résoudre à différents fichiers.

Donc, selon l'endroit où vous vous trouvez lorsque vous avez besoin d'un module, il est possible d'obtenir une instance différente du module. 

On dirait que les modules ne sont pas une solution simple à la création de singletons.

Edit: Ou peut-être sont-ils. Comme @mkoryak, je ne peux pas arriver à un cas où un seul fichier pourrait être résolu en noms de fichiers différents (sans utiliser de liens symboliques). Mais (comme @JohnnyHK commente), plusieurs copies d'un fichier dans différents répertoires node_modules seront chargées et stockées séparément.

19
mike

Un singleton dans node.js (ou dans le navigateur JS, par exemple) comme cela est totalement inutile.

Étant donné que les modules sont mis en cache et avec état, l’exemple donné sur le lien que vous avez fourni pourrait facilement être récrit beaucoup plus simplement:

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
18
Paul Armstrong

Vous n'avez besoin de rien de spécial pour faire un singleton en js, le code de l'article pourrait tout aussi bien être:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

En dehors de node.js (par exemple, dans le navigateur js), vous devez ajouter la fonction d'encapsuleur manuellement (cela se fait automatiquement dans node.js):

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

La seule réponse ici qui utilise les classes ES6

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

nécessite ce singleton avec:

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

Le seul problème ici est que vous ne pouvez pas transmettre de paramètres au constructeur de classe, mais vous pouvez les contourner en appelant manuellement une méthode init.

5
danday74

Les singletons vont bien dans JS, ils n'ont tout simplement pas besoin d'être aussi verbeux. 

Dans noeud si vous avez besoin d'un singleton, par exemple pour utiliser la même instance ORM/DB sur plusieurs fichiers de votre couche serveur, vous pouvez insérer la référence dans une variable globale. 

Il suffit d'écrire un module qui crée la variable globale s'il n'existe pas, puis renvoie une référence à celle-ci.

@ allen-luce avait raison avec son exemple de code de note de bas de page copié ici: 

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

module.exports = singleton.getInstance();

mais il est important de noter que l'utilisation du mot clé new est pas requis. Tout ancien objet, fonction, vie, etc. fonctionnera - il n'y a pas de OOP vaudou ici. 

points de bonus si vous fermez un objet dans une fonction qui y retourne une référence et que vous faites de cette fonction un objet global - même la réaffectation de la variable globale n'empiétera pas les instances déjà créées à partir de celle-ci - bien que cela puisse être discutable 

3
Adam Tolley

Garder les choses simples 

foo.js

function foo() {

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

  return bar;
};

module.exports = foo();

Alors juste

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