Singleton-Muster in nodejs - wird es benötigt?

95

Ich bin kürzlich auf diesen Artikel gestoßen, in dem es darum geht, einen Singleton in Node.js zu schreiben. Ich kenne die Dokumentation von require Staaten , die:

Module werden nach dem ersten Laden zwischengespeichert. Bei mehreren Aufrufen von wird require('foo')der Modulcode möglicherweise nicht mehrmals ausgeführt.

Es scheint also, dass jedes erforderliche Modul ohne den Singleton-Boilerplate-Code problemlos als Singleton verwendet werden kann.

Frage:

Bietet der obige Artikel eine umfassende Lösung zum Erstellen eines Singletons?

mkoryak
quelle
1
Hier ist eine 5 min. Erklärung zu diesem Thema (geschrieben nach v6 und npm3): medium.com/@lazlojuly/…
lazlojuly

Antworten:

56

Dies hat im Wesentlichen mit dem Zwischenspeichern von Knoten zu tun. Schlicht und einfach.

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

(v 6.3.1)

Caching

Module werden nach dem ersten Laden zwischengespeichert. Dies bedeutet (unter anderem), dass bei jedem erforderlichen Aufruf ('foo') genau dasselbe Objekt zurückgegeben wird, wenn es in dieselbe Datei aufgelöst wird.

Bei mehreren erforderlichen Anrufen ('foo') wird der Modulcode möglicherweise nicht mehrmals ausgeführt. Dies ist ein wichtiges Merkmal. Damit können "teilweise erledigte" Objekte zurückgegeben werden, so dass transitive Abhängigkeiten geladen werden können, selbst wenn sie Zyklen verursachen würden.

Wenn ein Modul Code mehrmals ausführen soll, exportieren Sie eine Funktion und rufen Sie diese Funktion auf.

Vorsichtsmaßnahmen beim Zwischenspeichern von Modulen

Module werden basierend auf ihrem aufgelösten Dateinamen zwischengespeichert. Da Module möglicherweise basierend auf dem Speicherort des aufrufenden Moduls in einen anderen Dateinamen aufgelöst werden (Laden aus den Ordnern node_modules), ist es keine Garantie, dass require ('foo') immer genau dasselbe Objekt zurückgibt, wenn es in verschiedene Dateien aufgelöst wird .

Bei Dateisystemen oder Betriebssystemen, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird, können unterschiedliche aufgelöste Dateinamen auf dieselbe Datei verweisen. Der Cache behandelt sie jedoch weiterhin als unterschiedliche Module und lädt die Datei mehrmals neu. Beispielsweise benötigen require ('./ foo') und require ('./ FOO') zwei verschiedene Objekte, unabhängig davon, ob ./foo und ./FOO dieselbe Datei sind oder nicht.

Also in einfachen Worten.

Wenn Sie einen Singleton wollen; ein Objekt exportieren .

Wenn Sie keinen Singleton wollen; exportiere eine Funktion (und mache Sachen / gib Sachen zurück / was auch immer in dieser Funktion).

Um ganz klar zu sein, wenn Sie dies richtig machen, sollte es funktionieren, schauen Sie unter https://stackoverflow.com/a/33746703/1137669 (Allen Luces Antwort). Im Code wird erklärt, was passiert, wenn das Caching aufgrund unterschiedlich aufgelöster Dateinamen fehlschlägt. Wenn Sie jedoch IMMER denselben Dateinamen verwenden, sollte dies funktionieren.

Update 2016

Erstellen eines echten Singletons in node.js mit es6-Symbolen Eine weitere Lösung : in diesem Link

Update 2020

Diese Antwort bezieht sich auf CommonJS (Node.js eigene Methode zum Importieren / Exportieren von Modulen). Node.js wird höchstwahrscheinlich auf ECMAScript-Module umsteigen : https://nodejs.org/api/esm.html (ECMAScript ist der wirkliche Name von JavaScript, wenn Sie es nicht wussten)

Lesen Sie bei der Migration zu ECMAScript vorerst Folgendes: https://nodejs.org/api/esm.html#esm_writing_dual_packages_while_avoiding_or_minimizing_hazards

K - Die Toxizität in SO nimmt zu.
quelle
3
Wenn Sie einen Singleton wollen; exportiere ein Objekt ... das hat
dir
1
Dies ist eine schlechte Idee - aus vielen Gründen, die an anderer Stelle auf dieser Seite angegeben sind -, aber das Konzept ist im Wesentlichen gültig, dh, dass unter den festgelegten nominellen Umständen die Behauptungen in dieser Antwort wahr sind. Wenn Sie einen schnellen und schmutzigen Singleton möchten, wird dies wahrscheinlich funktionieren - starten Sie einfach keine Shuttles mit dem Code.
Adam Tolley
@AdamTolley "aus vielen Gründen, die an anderer Stelle auf dieser Seite angegeben sind", beziehen Sie sich auf Symlinking-Dateien oder falsch geschriebene Dateinamen, die anscheinend nicht denselben Cache verwenden? In der Dokumentation wird das Problem in Bezug auf Dateisysteme oder Betriebssysteme angegeben, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird. In Bezug auf Symlinking können Sie hier mehr darüber lesen, wie es unter github.com/nodejs/node/issues/3402 besprochen wurde . Auch wenn Sie Dateien verknüpfen oder Ihr Betriebssystem und Ihren Knoten nicht richtig verstehen, sollten Sie nicht in der Nähe der Luft- und Raumfahrtindustrie sein;), ich verstehe jedoch Ihren Standpunkt ^^.
K - Die Toxizität in SO nimmt zu.
@KarlMorrison - nur weil die Dokumentation dies nicht garantiert, weil es sich um ein nicht spezifiziertes Verhalten zu handeln scheint oder aus irgendeinem anderen rationalen Grund, diesem bestimmten Verhalten der Sprache nicht zu vertrauen. Möglicherweise funktioniert der Cache in einer anderen Implementierung anders, oder Sie arbeiten gerne in REPLs und untergraben die Caching-Funktion insgesamt. Mein Punkt ist, dass der Cache ein Implementierungsdetail ist und die Verwendung als Singleton-Äquivalent ein kluger Hack ist. Ich liebe clevere Hacks, aber sie sollten differenziert werden, das ist alles - (auch niemand startet Shuttles mit Knoten, ich war dumm)
Adam Tolley
136

All dies ist zu kompliziert. Es gibt eine Denkschule, die besagt, dass Designmuster Mängel der tatsächlichen Sprache aufweisen.

Sprachen mit prototypbasiertem OOP (klassenlos) benötigen überhaupt kein Singleton-Muster. Sie erstellen einfach ein einzelnes (Tonnen-) Objekt im laufenden Betrieb und verwenden es dann.

Ja, Module im Knoten werden standardmäßig zwischengespeichert, können jedoch optimiert werden, wenn Sie beispielsweise Moduländerungen im laufenden Betrieb laden möchten.

Aber ja, wenn Sie gemeinsam genutzte Objekte überall verwenden möchten, ist es in Ordnung, sie in einen Modulexport zu integrieren. Komplizieren Sie es einfach nicht mit "Singleton-Muster", es wird in JavaScript nicht benötigt.


quelle
27
Es ist seltsam, dass niemand Upvotes bekommt ... habe eine +1 fürThere is a school of thought which says design patterns are showing deficiencies of actual language.
Esailija
64
Singletons sind kein Anti-Pattern.
wprl
4
@herby scheint eine zu spezifische (und daher falsche) Definition des Singleton-Musters zu sein.
wprl
18
In der Dokumentation heißt es: "Bei mehreren erforderlichen Anrufen ('foo') wird der Modulcode möglicherweise nicht mehrmals ausgeführt." Es heißt "darf nicht", es heißt nicht "wird nicht", daher ist die Frage, wie sichergestellt werden kann, dass die Modulinstanz nur einmal in einer Anwendung erstellt wird, aus meiner Sicht eine gültige Frage.
Xorcus
9
Es ist irreführend, dass dies die richtige Antwort auf diese Frage ist. Wie @mike unten ausgeführt hat, wird ein Modul möglicherweise mehrmals geladen und Sie haben zwei Instanzen. Ich stoße auf dieses Problem, bei dem ich nur eine Kopie von Knockout habe, aber zwei Instanzen erstellt werden, weil das Modul zweimal geladen wird.
Dgaviola
25

Nein. Wenn das Modul-Caching des Knotens fehlschlägt, schlägt dieses Singleton-Muster fehl. Ich habe das Beispiel so geändert, dass es unter OSX sinnvoll ausgeführt wird:

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

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

Dies gibt die vom Autor erwartete Ausgabe:

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

Eine kleine Modifikation verhindert jedoch das Caching. Gehen Sie unter OSX folgendermaßen vor:

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

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

Oder unter Linux:

% ln singleton.js singleton2.js

Ändern Sie dann die sg2erforderliche Zeile in:

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

Und bam , der Singleton ist besiegt:

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

Ich kenne keinen akzeptablen Weg, um das zu umgehen. Wenn Sie wirklich das Bedürfnis haben, etwas Singleton-ähnliches zu machen und den globalen Namespace (und die vielen Probleme, die daraus entstehen können) zu verschmutzen, können Sie den Autor getInstance()und die exportsZeilen ändern in:

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

module.exports = singleton.getInstance();

Trotzdem bin ich noch nie auf ein Produktionssystem gestoßen, in dem ich so etwas tun musste. Ich hatte auch nie das Bedürfnis, das Singleton-Muster in Javascript zu verwenden.

Allen Luce
quelle
21

Sehen Sie sich die Vorsichtsmaßnahmen zum Zwischenspeichern von Modulen in den Moduldokumenten etwas genauer an:

Module werden basierend auf ihrem aufgelösten Dateinamen zwischengespeichert. Da Module je nach Speicherort des aufrufenden Moduls in einen anderen Dateinamen aufgelöst werden können (Laden aus den Ordnern node_modules), ist es keine Garantie, dass require ('foo') immer genau dasselbe Objekt zurückgibt , wenn es in verschiedene Dateien aufgelöst wird .

Je nachdem, wo Sie sich befinden, wenn Sie ein Modul benötigen, können Sie eine andere Instanz des Moduls abrufen.

Klingt so, als wären Module keine einfache Lösung zum Erstellen von Singletons.

Edit: Oder vielleicht sind sie es . Wie bei @mkoryak kann ich keinen Fall finden, in dem eine einzelne Datei in verschiedene Dateinamen aufgelöst werden könnte (ohne Verwendung von Symlinks). Aber (wie @JohnnyHK kommentiert) werden mehrere Kopien einer Datei in verschiedenen node_modulesVerzeichnissen jeweils separat geladen und gespeichert.

Mike
quelle
ok, ich habe das 3 mal gelesen und ich kann immer noch nicht an ein Beispiel denken, bei dem es sich in einen anderen Dateinamen auflösen würde. Hilfe?
Mkoryak
1
@mkoryak Ich denke, das bezieht sich auf Fälle, in denen Sie zwei verschiedene Module benötigen, von node_modulesdenen jedes vom selben Modul abhängt, aber es gibt separate Kopien dieses abhängigen Moduls im node_modulesUnterverzeichnis jedes der beiden verschiedenen Module.
JohnnyHK
@mike Sie sind hier richtig, dass das Modul mehrmals instanziiert wird, wenn es über verschiedene Pfade referenziert wird. Ich bin auf den Fall gestoßen, als ich Unit-Tests für die Servermodule geschrieben habe. Ich brauche eine Art Singleton-Instanz. wie erreicht man das?
Sushil
Ein Beispiel könnten relative Pfade sein. z.B. Gegeben require('./db')ist in zwei separaten Dateien, der Code für das dbModul wird zweimal ausgeführt
willscripted
9
Ich hatte gerade einen bösen Fehler, da das Knotenmodul-System case-insenstivie ist. Ich habe require('../lib/myModule.js');eine Datei und eine require('../lib/mymodule.js');andere aufgerufen und es wurde nicht das gleiche Objekt geliefert.
Heyarne
18

Ein solcher Singleton in node.js (oder in Browser JS) ist völlig unnötig.

Da Module zwischengespeichert und statusbehaftet sind, könnte das Beispiel auf dem von Ihnen angegebenen Link viel einfacher umgeschrieben werden:

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
Paul Armstrong
quelle
5
Docs sagt „ kann nicht das Modul Code führen zu mehrfach ausgeführt werden“, so dass es möglich ist , dass es mehrmals aufgerufen werden, und wenn dieser Code erneut ausgeführt wird, wird der socketList zurückgesetzt auf eine leere Liste sein
Jonathan.
3
@ Jonathan. Der Kontext in der Dokumentation um dieses Zitat scheint einen ziemlich überzeugend Fall zu machen , das nicht kann in einer RFC-Stil verwendet wird, um NICHT MUSS .
Michael
6
@ Michael "Mai" ist so ein lustiges Wort. Lust auf ein Wort, das, wenn es negiert wird, entweder "vielleicht nicht" oder "definitiv nicht" bedeutet.
OJFord
1
Das may notgilt, wenn Sie npm linkandere Module während der Entwicklung. Seien Sie also vorsichtig, wenn Sie Module verwenden, die auf einer einzelnen Instanz basieren, z. B. einem eventBus.
Mediafreakch
10

Die einzige Antwort hier, die ES6-Klassen verwendet

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

Benötigen Sie diesen Singleton mit:

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

Das einzige Problem hierbei ist, dass Sie keine Parameter an den Klassenkonstruktor übergeben können, dies kann jedoch durch manuelles Aufrufen einer initMethode umgangen werden .

danday74
quelle
Die Frage ist "werden Singletons benötigt", nicht "wie schreibt man eine"
mkoryak
@ danday74 Wie können wir dieselbe Instanz summaryin einer anderen Klasse verwenden, ohne sie erneut zu initialisieren?
M Faisal Hameed
1
In Node.js benötigen Sie es einfach in einer anderen Datei ... const summary = require ('./ SummaryModule') ... und es wird dieselbe Instanz sein. Sie können dies testen, indem Sie eine Mitgliedsvariable erstellen und ihren Wert in einer Datei festlegen, für die dies erforderlich ist, und dann ihren Wert in einer anderen Datei abrufen, für die dies erforderlich ist. Es sollte der eingestellte Wert sein.
danday74
9

Sie brauchen nichts Besonderes, um einen Singleton in js zu erstellen. Der Code im Artikel könnte genauso gut sein:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

Außerhalb von node.js (z. B. in Browser js) müssen Sie die Wrapper-Funktion manuell hinzufügen (dies erfolgt automatisch in node.js):

var singleton = function() {
    var socketList = {};
    return {
        add: function() {},
        ...
    };
}();
Esailija
quelle
Wie von @Allen Luce hervorgehoben, schlägt auch das Singleton-Muster fehl, wenn das Caching des Knotens fehlschlägt.
Rakeen
6

Singletons sind in JS in Ordnung, sie müssen einfach nicht so ausführlich sein.

Wenn Sie im Knoten einen Singleton benötigen, um beispielsweise dieselbe ORM / DB-Instanz für verschiedene Dateien in Ihrer Serverschicht zu verwenden, können Sie die Referenz in eine globale Variable einfügen.

Schreiben Sie einfach ein Modul, das die globale Variable erstellt, wenn sie nicht vorhanden ist, und geben Sie dann einen Verweis darauf zurück.

@ Allen-luce hatte es richtig mit seinem hier kopierten Fußnoten-Codebeispiel:

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

module.exports = singleton.getInstance();

Es ist jedoch wichtig zu beachten, dass die Verwendung des newSchlüsselworts nicht erforderlich ist. Jedes alte Objekt, jede Funktion, jedes Leben usw. wird funktionieren - hier findet kein OOP-Voodoo statt.

Bonuspunkte, wenn Sie ein Objekt in einer Funktion schließen, die einen Verweis darauf zurückgibt, und diese Funktion zu einer globalen Funktion machen - selbst eine Neuzuweisung der globalen Variablen wird die bereits daraus erstellten Instanzen nicht blockieren - obwohl dies fraglich nützlich ist.

Adam Tolley
quelle
du brauchst nichts davon. Sie können dies einfach tun, module.exports = new Foo()weil module.exports nicht erneut ausgeführt wird, es sei denn, Sie tun etwas wirklich Dummes
mkoryak
Sie sollten sich NICHT auf die Nebenwirkungen der Implementierung verlassen. Wenn Sie eine einzelne Instanz benötigen, binden Sie sie einfach an eine globale Instanz, falls sich die Implementierung ändert.
Adam Tolley
Die obige Antwort war auch ein Missverständnis der ursprünglichen Frage: "Soll ich Singletons in JS verwenden oder macht die Sprache sie unnötig?", Was auch bei vielen anderen Antworten ein Problem zu sein scheint. Ich stehe zu meiner Empfehlung, die erforderliche Implementierung nicht als Ersatz für eine ordnungsgemäße, explizite Singleton-Implementierung zu verwenden.
Adam Tolley
1

Einfach halten.

foo.js

function foo() {

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

  return bar;
};

module.exports = foo();

Dann einfach

var foo = require(__dirname + 'foo');
foo.doSomething('Hello', function(result){ console.log(result); });
Iss bei Joes
quelle
Die Frage ist "werden Singletons benötigt", nicht "wie schreibt man eine"
mkoryak