112 votes

Le modèle singleton dans nodejs - est-il 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. Les appels multiples à require('foo') ne doit pas entraîner l'exécution multiple du code du module.

Il semble donc que chaque module requis puisse être facilement utilisé comme singleton sans le code boilerplate singleton.

Question :

L'article ci-dessus fournit-il une solution indirecte pour créer un singleton ?

151voto

Tout ce qui précède est trop compliqué. Il y a une école de pensée qui dit que les modèles de conception montrent les déficiences du langage actuel.

Les langages de POO basés sur des prototypes (sans classe) n'ont pas du tout besoin d'un modèle de singleton. Il suffit de créer un objet singleton à la volée, puis de l'utiliser.

En ce qui concerne les modules dans le nœud, oui, par défaut, ils sont mis en cache, mais il est possible de le modifier, par exemple si vous souhaitez un chargement à chaud des changements de module.

Mais oui, si vous voulez utiliser l'objet partagé partout, le mettre dans un module exports est bien. Mais ne compliquez pas les choses avec le "singleton pattern", qui n'est pas nécessaire en JavaScript.

75voto

Karl Morrison Points 774

Cela a essentiellement à voir avec la mise en cache de nodejs. C'est aussi simple que ça.

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

(v 6.3.1)

Mise en cache

Les modules sont mis en cache après leur premier chargement. Cela signifie (entre autres choses) que chaque appel à require('foo') obtiendra exactement le même objet retourné, s'il résout le même fichier. fichier.

Des appels multiples à require('foo') peuvent ne pas entraîner le code du module à être exécuté plusieurs fois. Il s'agit d'une fonctionnalité importante. Grâce à elle, des objets "partiellement réalisés" peuvent être retournés, permettant ainsi aux dépendances transitives transitives d'être chargées même si elles provoquent des cycles.

Si vous voulez qu'un module exécute du code plusieurs fois, exportez alors une fonction, et appelez cette fonction.

Mises en garde concernant la mise en cache des modules

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

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 pointer vers le même fichier, mais le cache les traitera tout de même comme des modules différents et rechargera le fichier plusieurs fois. Par exemple, require('./foo') et require('./FOO') renvoient deux objets différents, indépendamment du fait que ./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 ; exporter une fonction (et faire des choses/renvoyer des choses/autres dans cette fonction).

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

Mise à jour 2016

créer un vrai singleton dans node.js avec des symboles es6 Une autre solution : dans ce lien

Mise à jour 2020

Cette réponse fait référence à CommonJS (la façon propre à Node.js d'importer/exporter des modules). Node.js va très probablement passer à Modules ECMAScript : https://nodejs.org/api/esm.html (ECMAScript est le vrai nom de JavaScript, si vous ne le saviez pas).

Lors de la migration vers ECMAScript, lisez ce qui suit pour l'instant : https://nodejs.org/api/esm.html#esm_writing_dual_packages_while_avoiding_or_minimizing_hazards

31voto

Allen Luce Points 4152

Non. Lorsque la mise en cache des modules de Node échoue, ce modèle de singleton échoue. J'ai modifié l'exemple pour qu'il fonctionne correctement sous 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 que l'auteur avait prévu :

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

Mais une petite modification fait échouer la mise en cache. Sous OSX, faites ceci :

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, sur Linux :

% ln singleton.js singleton2.js

Ensuite, changez le sg2 exiger la ligne à :

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

Et bam le singleton est vaincu :

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

Je ne connais pas de moyen acceptable de contourner ce problème. Si vous ressentez vraiment le besoin de faire quelque chose qui ressemble à un singleton et que vous êtes d'accord pour polluer l'espace de noms global (et les nombreux problèmes qui peuvent en résulter), vous pouvez changer le nom de l'auteur en getInstance() y exports des lignes vers :

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

module.exports = singleton.getInstance();

Cela dit, je n'ai jamais rencontré de situation sur un système de production où j'ai eu besoin de faire quelque chose comme ça. Je n'ai également jamais ressenti le besoin d'utiliser le modèle singleton en Javascript.

22voto

mike Points 5146

En regardant un peu plus loin, le Mises en garde concernant la mise en cache des modules dans la documentation sur les modules :

Les modules sont mis en cache en fonction de leur nom de fichier résolu. Puisque les modules peuvent être résolus avec un nom de fichier différent en fonction de l'emplacement du module appelant (chargement à partir des dossiers node_modules), il ne s'agit pas d'une garantie que require('foo') retournera toujours exactement le même objet, s'il devait résoudre des fichiers différents.

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

On dirait que les modules sont pas une solution simple pour créer des singletons.

Edit : Ou peut-être qu'ils sont . Comme @mkoryak, je n'arrive pas à trouver un cas où un seul fichier pourrait être résolu par des noms de fichiers différents (sans utiliser de liens symboliques). Mais (comme le commente @JohnnyHK), des copies multiples d'un fichier dans différents fichiers node_modules seront chacun chargés et stockés séparément.

19voto

Paul Armstrong Points 5117

Un singleton dans node.js (ou dans un navigateur JS, d'ailleurs) comme celui-là est complètement inutile.

Étant donné que les modules sont mis en cache et dépendent de l'é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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X