Wzór Singletona w nodejs - czy jest potrzebny?

Ostatnio natknąłem się na Ten artykuł o tym, jak napisać singleton w węźle.js. Znam dokumentację require stwierdza , że:

Moduły są buforowane po pierwszym załadowaniu. Wielokrotne wywołania require('foo') mogą nie powodować wielokrotnego wykonania kodu modułu.

Wydaje się więc, że każdy wymagany moduł może być łatwo użyty jako singleton bez kodu Singleton boilerplate.

Pytanie:

Czy powyższy artykuł dostarcza okrągły o rozwiązanie do tworzenia singleton?

Author: staaar, 2012-11-01

9 answers

Ma to zasadniczo związek z buforowaniem nodejs. Jasne i proste.

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

(v 6.3.1)

Buforowanie

Moduły są buforowane po pierwszym załadowaniu. Oznacza to (między innymi), że każde wywołanie wymagające ('foo') otrzyma dokładnie ten sam obiekt zwrócony, jeśli zostanie rozwiązany do tego samego plik.

Wielokrotne wywołania wymagające ('foo') mogą nie spowodować, że kod modułu be wykonywany wielokrotnie. Jest to ważna cecha. Z nim, obiekty" częściowo wykonane " mogą być zwracane, umożliwiając tym samym Transitional zależności, które mają być załadowane nawet wtedy, gdy spowodowałyby cykle.

Jeśli chcesz, aby moduł wykonywał kod wielokrotnie, Eksportuj funkcję, i wywołać tę funkcję.

Buforowanie Modułów

Moduły są buforowane na podstawie ich rozwiązanej nazwy pliku. Od maja rozwiązuje do innej nazwy pliku na podstawie miejsce wywołania modułu (ładowanie z folderów node_modules), nie jest gwarancją, że require ('foo') zawsze zwróci dokładnie ten sam obiekt, jeśli rozwiązuj do różnych plików.

Dodatkowo, w systemach plików niewrażliwych na wielkość liter lub systemach operacyjnych, różne nazwy plików mogą wskazywać na ten sam plik, ale pamięć podręczna nadal będzie traktować je jako różne moduły i przeładuje plik wiele razy. Na przykład require ('./ foo') I require ("./ FOO) zwraca dwa różne obiekty, niezależnie od tego, czy/ foo and ./ FOO to ten sam plik.

Więc w prostych słowach.

Jeśli chcesz mieć Singleton; wyeksportuj obiekt.

If you do not want a Singleton; export a function (and do stuff/return stuff/whatever in that function).

aby być bardzo jasne, jeśli robisz to poprawnie, to powinno działać, spójrz na https://stackoverflow.com/a/33746703/1137669 (odpowiedź Allena Luce ' a). Informatyka wyjaśnia w kodzie, co się dzieje, gdy buforowanie nie powiedzie się z powodu różnie rozwiązanych nazw plików. Ale jeśli zawsze zdecydujesz się na tę samą nazwę pliku, powinno to działać.

 23
Author: Karl Morrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-06 11:26:37

Wszystko to jest zbyt skomplikowane. Istnieje szkoła myślenia, która mówi, że wzorce projektowe wykazują braki rzeczywistego języka.

Języki z OOP opartym na prototypach (classless) w ogóle nie potrzebują wzorca Singletona. Po prostu tworzysz pojedynczy (tonowy) obiekt w locie, a następnie go używasz.

Jeśli chodzi o moduły w węźle, tak, domyślnie są one buforowane, ale można je poprawić na przykład, jeśli chcesz załadować zmiany modułów na gorąco.

Ale tak, jeśli chcesz użyć współdzielony obiekt wszędzie, umieszczenie go w eksporcie modułu jest w porządku. Po prostu nie komplikuj tego z "singleton pattern", nie ma potrzeby tego w JavaScript.

 127
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-01 14:35:58

Singleton w węźle.js (lub w przeglądarce JS, jeśli o to chodzi) jest zupełnie niepotrzebny.

Ponieważ moduły są buforowane i stanowe, przykład podany na podanym linku można łatwo przepisać znacznie prościej:

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
Author: Paul Armstrong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-01 14:32:36

Patrząc nieco dalej na zastrzeżenia buforowania modułów W dokumentach modułów:

Moduły są buforowane na podstawie ich rozwiązanej nazwy pliku. Ponieważ moduły mogą rozwiązać do innej nazwy pliku na podstawie lokalizacji wywołującego moduł (ładowanie z folderów node_modules), nie jest gwarancją, że require('foo') zawsze zwróci dokładnie ten sam obiekt, jeśli rozwiąże się do różnych plików.

/ Align = "left" / wymagając modułu, możliwe jest uzyskanie innej instancji modułu.

Wygląda na to, że moduły są , a nie prostym rozwiązaniem do tworzenia singletonów.

Edit: a może oni . Podobnie jak @ mkoryak, nie mogę wymyślić przypadku, w którym pojedynczy plik może rozwiązać się na różne nazwy plików (bez użycia dowiązań symbolicznych). Ale (jak komentuje @ JohnnyHK), wiele kopii pliku w różnych katalogach node_modules będzie ładowanych i przechowywanych oddzielnie.

 18
Author: mike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-03 15:49:38

Nie. gdy buforowanie modułu węzła nie powiedzie się, ten wzór Singletona nie powiedzie się. Zmodyfikowałem przykład, aby działał sensownie na 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());

Daje to wynik, którego autor oczekiwał:

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

Ale mała modyfikacja pokonuje buforowanie. Na OSX zrób to:

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

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

Lub, na Linuksie:

% ln singleton.js singleton2.js

Następnie zmień linię sg2 require na:

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

I BAM , singleton zostaje pokonany:

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

Nie znam akceptowalnego sposób na obejście tego. Jeśli naprawdę czujesz potrzebę stworzenia czegoś podobnego do Singletona i nie masz nic przeciwko zanieczyszczeniu globalnej przestrzeni nazw( i wielu problemom, które mogą wynikać), możesz zmienić linie getInstance() i exports autora na:

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

module.exports = singleton.getInstance();

To powiedziawszy, nigdy nie spotkałem się z sytuacją na systemie produkcyjnym, w której musiałem zrobić coś takiego. Nigdy też nie czułem potrzeby używania wzoru Singletona w Javascript.

 17
Author: Allen Luce,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-01 19:44:34

Nie potrzebujesz nic specjalnego, aby zrobić singleton w js, kod w artykule równie dobrze mógłby być:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

Zewnętrzny węzeł.js (na przykład w przeglądarce js), musisz dodać funkcję wrapper ręcznie (odbywa się to automatycznie w węźle."js"): {]}

var singleton = function() {
    var socketList = {};
    return {
        add: function() {},
        ...
    };
}();
 10
Author: Esailija,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-01 18:22:18

Jedyna odpowiedź, która używa klas ES6

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

Wymagaj tego singletonu z:

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

Jedyny problem polega na tym, że nie można przekazać param do konstruktora klasy, ale można to obejść ręcznie wywołując metodę init.

 4
Author: danday74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-05 12:56:09

Singletony są dobre w JS, po prostu nie muszą być takie gadatliwe.

W node jeśli potrzebujesz Singletona, na przykład, aby użyć tej samej instancji ORM / DB w różnych plikach w warstwie serwerowej, możesz umieścić odniesienie do zmiennej globalnej.

Po prostu napisz moduł, który utworzy globalny var, jeśli nie istnieje, a następnie zwróci odniesienie do niego.

@allen-luce miał rację z przykładowym kodem przypisu skopiowanym tutaj:

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

module.exports = singleton.getInstance();

Ale ważne jest, aby zauważ, że użycie słowa kluczowego new jest wymagane , a nie . Każdy stary obiekt, funkcja, życie itp. będzie działać - nie ma voodoo OOP dzieje się tutaj.

Punkty bonusowe jeśli zamkniesz jakiś obj wewnątrz funkcji, która zwraca odniesienie do niej, i uczynisz tę funkcję globalną - wtedy nawet zmiana zmiennej globalnej nie zatrze instancji już utworzonych z niej-choć jest to wątpliwie przydatne.

 3
Author: Adam Tolley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-06 15:33:36

/ Align = "left" /

foo.js

function foo() {

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

  return bar;
};

module.exports = foo();

Then just

var foo = require(__dirname + 'foo');
foo.doSomething('Hello', function(result){ console.log(result); });
 0
Author: Eat at Joes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-08 12:57:30