Wzór singletona w nodejs - czy jest potrzebny?

97

Niedawno natknąłem się na ten artykuł o tym, jak napisać singleton w Node.js. Znam dokumentację require stanów, które:

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

Wygląda więc na to, że każdy wymagany moduł może być łatwo użyty jako singleton bez pojedynczego kodu kotłowego.

Pytanie:

Czy powyższy artykuł zawiera opis rozwiązania tworzenia singletona?

mkoryak
źródło
1
Oto 5 min. wyjaśnienie na ten temat (napisane po v6 i npm3): medium.com/@lazlojuly/…
lazlojuly

Odpowiedzi:

60

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

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

(wersja 6.3.1)

Buforowanie

Moduły są buforowane po pierwszym załadowaniu. Oznacza to (między innymi), że każde wywołanie request ('foo') zwróci dokładnie ten sam obiekt, jeśli miałoby to zostać rozwiązane do tego samego pliku.

Wielokrotne wywołania require („foo”) mogą nie powodować wielokrotnego wykonywania kodu modułu. To ważna cecha. Dzięki niemu można zwrócić obiekty „częściowo wykonane”, co pozwala na ładowanie zależności przechodnich, nawet jeśli powodowałyby cykle.

Jeśli chcesz, aby moduł wykonywał kod wiele razy, wyeksportuj funkcję i wywołaj tę funkcję.

Zastrzeżenia dotyczące buforowania modułów

Moduły są buforowane na podstawie ich rozstrzygniętej nazwy pliku. Ponieważ moduły mogą być rozwiązywane do innej nazwy pliku w oparciu o lokalizację modułu wywołującego (ładowanie z folderów node_modules), nie jest gwarancją, że require ('foo') zawsze zwróci dokładnie ten sam obiekt, jeśli będzie to inne pliki .

Ponadto w systemach plików lub systemach operacyjnych bez rozróżniania wielkości liter różne rozpoznane nazwy plików mogą wskazywać na ten sam plik, ale pamięć podręczna nadal będzie traktować je jako różne moduły i wielokrotnie ładuje plik. Na przykład require („./ foo”) i require („./ FOO”) zwracają dwa różne obiekty, niezależnie od tego, czy ./foo i ./FOO są tym samym plikiem.

Więc w prostych słowach.

Jeśli chcesz Singleton; wyeksportować obiekt .

Jeśli nie chcesz Singletona; eksportuj funkcję (i wykonaj rzeczy / zwrócenie rzeczy / cokolwiek w tej funkcji).

Aby być BARDZO jasnym, jeśli zrobisz to poprawnie, powinno działać, spójrz na https://stackoverflow.com/a/33746703/1137669 (odpowiedź Allena Luce'a). Wyjaśnia w kodzie, co się dzieje, gdy buforowanie nie powiedzie się z powodu inaczej rozwiązanych nazw plików. Ale jeśli ZAWSZE wybierasz tę samą nazwę pliku, to powinno działać.

Aktualizacja 2016

tworzenie prawdziwego singletona w node.js z symbolami es6 Inne rozwiązanie : w tym linku

Zaktualizuj 2020

Ta odpowiedź odnosi się do CommonJS (własnego sposobu importowania / eksportowania modułów przez Node.js). Node.js najprawdopodobniej przejdzie na moduły ECMAScript : https://nodejs.org/api/esm.html (ECMAScript to prawdziwa nazwa JavaScript, jeśli nie wiesz)

Podczas migracji do ECMAScript przeczytaj na razie następujące informacje: https://nodejs.org/api/esm.html#esm_writing_dual_packages_while_avoiding_or_minimizing_hazards

basickarl
źródło
3
Jeśli chcesz Singleton; eksportuj obiekt ... który pomógł dzięki
danday74
1
To trochę zły pomysł - z wielu powodów podanych w innym miejscu na tej stronie - ale koncepcja jest zasadniczo ważna, co oznacza, że ​​w ustalonych nominalnych okolicznościach twierdzenia zawarte w tej odpowiedzi są prawdziwe. Jeśli chcesz szybkiego i brudnego singletona, prawdopodobnie zadziała - po prostu nie uruchamiaj żadnych wahadłowców z kodem.
Adam Tolley
@AdamTolley „z wielu powodów podanych w innym miejscu na tej stronie”, czy odnosisz się do plików dowiązań symbolicznych lub błędnej pisowni nazw plików, które najwyraźniej nie używają tej samej pamięci podręcznej? W dokumentacji stwierdza się problem dotyczący systemów plików lub systemów operacyjnych bez rozróżniania wielkości liter. Jeśli chodzi o linkowanie symboliczne, możesz przeczytać więcej tutaj, ponieważ zostało to omówione na github.com/nodejs/node/issues/3402 . Również jeśli tworzysz dowiązania symboliczne do plików lub nie rozumiesz poprawnie swojego systemu operacyjnego i węzła, nie powinieneś znajdować się w pobliżu przemysłu lotniczego;), rozumiem jednak twój punkt widzenia ^^.
basickarl
@KarlMorrison - tylko dlatego, że dokumentacja tego nie gwarantuje, fakt, że wydaje się to być nieokreślonym zachowaniem lub jakikolwiek inny racjonalny powód, aby nie ufać temu konkretnemu zachowaniu języka. Być może pamięć podręczna działa inaczej w innej implementacji lub lubisz pracować w REPL i całkowicie obalić funkcję buforowania. Chodzi mi o to, że pamięć podręczna jest szczegółem implementacji, a użycie jej jako odpowiednika w postaci singletona to sprytny hack. Uwielbiam sprytne hacki, ale powinny być zróżnicowane, to wszystko - (też nikt nie uruchamia promów z węzłem, byłem głupi)
Adam Tolley
138

Wszystkie powyższe są zbyt skomplikowane. Istnieje szkoła myślenia, która mówi, że wzorce projektowe wykazują braki w prawdziwym języku.

Języki z OOP opartym na prototypach (bezklasowych) w ogóle nie potrzebują pojedynczego wzorca. 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 dostosować, na przykład, jeśli chcesz ładować zmiany modułów na gorąco.

Ale tak, jeśli chcesz używać wspólnego obiektu na całym świecie, umieszczenie go w eksporcie modułu jest w porządku. Po prostu nie komplikuj tego za pomocą „pojedynczego wzorca”, nie ma takiej potrzeby w JavaScript.


źródło
27
To dziwne, że nikt nie ma pozytywnych głosów ... masz +1 zaThere is a school of thought which says design patterns are showing deficiencies of actual language.
Esailija
64
Singletony nie są anty-wzorcem.
wprl
5
@herby, wydaje się być zbyt szczegółową (a zatem niepoprawną) definicją wzorca pojedynczego.
wprl
20
W dokumentacji czytamy: „Wiele wywołań do require ('foo') nie może powodować wielokrotnego wykonywania kodu modułu.”. Mówi „może nie”, nie mówi „nie będzie”, więc pytanie, jak upewnić się, że instancja modułu zostanie utworzona tylko raz w aplikacji, jest z mojego punktu widzenia ważnym pytaniem.
xorcus
9
Mylące jest, że jest to prawidłowa odpowiedź na to pytanie. Jak @mike wskazał poniżej, możliwe jest, że moduł zostanie załadowany więcej niż raz i masz dwie instancje. Uderzam w ten problem, w którym mam tylko jedną kopię Knockout, ale są tworzone dwie instancje, ponieważ moduł jest ładowany dwukrotnie.
dgaviola
26

Nie. Gdy buforowanie modułu Node nie powiedzie się, pojedynczy wzorzec 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, jakiego przewidywał autor:

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

Ale mała modyfikacja pokonuje buforowanie. W systemie 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 w systemie Linux:

% ln singleton.js singleton2.js

Następnie zmień sg2wymaganą linię na:

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

I bam , singleton został pokonany:

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

Nie znam akceptowalnego sposobu obejścia tego. Jeśli naprawdę czujesz potrzebę stworzenia czegoś podobnego do singletona i nie przeszkadza ci zanieczyszczanie globalnej przestrzeni nazw (i wiele problemów, które mogą z tego wyniknąć), możesz zmienić autora getInstance()i exportswiersze 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ą w systemie produkcyjnym, w której musiałbym zrobić coś takiego. Nigdy też nie czułem potrzeby używania wzorca singleton w Javascript.

Allen Luce
źródło
21

Patrząc nieco dalej na zastrzeżenia dotyczące buforowania modułów w dokumentacji modułów:

Moduły są buforowane na podstawie ich rozstrzygniętej nazwy pliku. Ponieważ moduły mogą być rozwiązywane do innej nazwy pliku w oparciu o lokalizację modułu wywołującego (ładowanie z folderów node_modules), nie jest gwarancją, że require ('foo') zawsze zwróci dokładnie ten sam obiekt, jeśli będzie to inne pliki .

Tak więc, w zależności od tego, gdzie jesteś, gdy potrzebujesz modułu, można uzyskać inną instancję modułu.

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

Edycja: A może . Podobnie jak @mkoryak, nie mogę wymyślić przypadku, w którym pojedynczy plik mógłby zostać rozwiązany na różne nazwy plików (bez użycia dowiązań symbolicznych). Ale (jak komentarze @JohnnyHK), wiele kopii pliku w różnych node_moduleskatalogach będzie ładowanych i przechowywanych oddzielnie.

mikrofon
źródło
ok, przeczytałem to 3 razy i nadal nie mogę pomyśleć o przykładzie, w którym rozwiązałoby się to z inną nazwą pliku. Wsparcie?
mkoryak
1
@mkoryak Myślę, że odnosi się to do przypadków, w których wymagane są dwa różne moduły, z node_modulesktórych każdy zależy od tego samego modułu, ale istnieją oddzielne kopie tego zależnego modułu w node_modulespodkatalogu każdego z dwóch różnych modułów.
JohnnyHK
@mike, jesteś tutaj, moduł jest tworzony wiele razy, gdy odwołuje się do niego różnymi ścieżkami. Trafiłem w przypadek, pisząc testy jednostkowe dla modułów serwera. Potrzebuję pojedynczej instancji. jak to osiągnąć?
Sushil,
Przykładem mogą być ścieżki względne. na przykład. Podany require('./db')jest w dwóch oddzielnych plikach, kod dbmodułu jest wykonywany dwukrotnie
zapisany w skryptach
9
Właśnie miałem paskudny błąd, ponieważ system modułów węzłów nie uwzględnia wielkości liter. wywołałem require('../lib/myModule.js');jeden plik, a require('../lib/mymodule.js');drugi i nie dostarczyłem tego samego obiektu.
heyarne
19

Singleton w node.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 w linku, który podałeś, 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
Paul Armstrong
źródło
5
Dokumenty mówią, że „ może nie powodować wielokrotnego wykonywania kodu modułu”, więc możliwe jest, że zostanie wywołany wiele razy, a jeśli ten kod zostanie wykonany ponownie, lista socketList zostanie zresetowana do pustej listy
Jonathan.
3
@Jonathan. Kontekst w dokumentach wokół tego cytatu wydaje się stanowić dość przekonujący przypadek, który może nie jest używany w stylu RFC NIE WOLNO .
Michael
6
@Michael „może” to takie zabawne słowo. Masz ochotę na słowo, które po zanegowaniu oznacza „może nie” lub „zdecydowanie nie” ..
OJFord,
1
Ma to may notzastosowanie, gdy npm linkpodczas opracowywania innych modułów. Dlatego zachowaj ostrożność podczas korzystania z modułów, które opierają się na pojedynczej instancji, takiej jak eventBus.
mediafreakch
10

Nie potrzebujesz nic specjalnego do zrobienia singletona w js, równie dobrze kod w artykule mógłby wyglądać następująco:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

Poza node.js (na przykład w przeglądarce js), musisz ręcznie dodać funkcję wrapper (jest to robione automatycznie w node.js):

var singleton = function() {
    var socketList = {};
    return {
        add: function() {},
        ...
    };
}();
Esailija
źródło
Jak zauważył @Allen Luce, jeśli buforowanie węzła nie powiedzie się, wzorzec singletona również zawodzi.
rakeen
10

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 singletona z:

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

Jedynym problemem jest to, że nie można przekazać parametrów do konstruktora klasy, ale można to obejść, ręcznie wywołując initmetodę.

danday74
źródło
pytanie jest „potrzebne są samotnymi”, a nie „jak można napisać”
mkoryak
@ danday74 Jak możemy użyć tej samej instancji summaryw innej klasie bez jej ponownego inicjowania?
M Faisal Hameed
1
W Node.js po prostu wymagaj tego w innym pliku ... const summary = require ('./ SummaryModule') ... i będzie to ta sama instancja. Możesz to sprawdzić, tworząc zmienną składową i ustawiając jej wartość w jednym pliku, który tego wymaga, a następnie pobierając jej wartość w innym, który tego wymaga. Powinna to być ustawiona wartość.
danday74
6

Singletony są w porządku w JS, po prostu nie muszą być tak gadatliwe.

W węźle, jeśli potrzebujesz singletona, na przykład, aby użyć tej samej instancji ORM / DB w różnych plikach w warstwie serwera, możesz umieścić odwołanie w zmiennej globalnej.

Po prostu napisz moduł, który tworzy globalną zmienną, jeśli nie istnieje, a następnie zwraca do niej odniesienie.

@ 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 pamiętać, że użycie newsłowa kluczowego nie jest wymagane. Każdy stary obiekt, funkcja, życie itp. Będzie działać - nie dzieje się tutaj żadne OOP voodoo.

punkty bonusowe, jeśli zamkniesz jakiś obiekt wewnątrz funkcji, która zwraca do niej odniesienie i sprawisz, że ta funkcja będzie globalna - wtedy nawet ponowne przypisanie zmiennej globalnej nie zbije instancji już utworzonych z niej - chociaż jest to wątpliwie przydatne.

Adam Tolley
źródło
nie potrzebujesz tego. możesz to zrobić, module.exports = new Foo()ponieważ module.exports nie uruchomi się ponownie, chyba że zrobisz coś naprawdę głupiego
mkoryak
Absolutnie NIE powinieneś polegać na efektach ubocznych implementacji. Jeśli potrzebujesz pojedynczej instancji, po prostu połącz ją z globalną, na wypadek zmiany implementacji.
Adam Tolley,
Powyższa odpowiedź była również niezrozumieniem pierwotnego pytania: „Czy powinienem używać singletonów w JS, czy też język czyni je niepotrzebnymi?”, Co wydaje się być problemem w przypadku wielu innych odpowiedzi. Sprzeciwiam się używaniu implementacji require jako zamiennika dla właściwej, jawnej implementacji singletona.
Adam Tolley
1

Upraszczanie.

foo.js

function foo() {

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

  return bar;
};

module.exports = foo();

Wtedy po prostu

var foo = require(__dirname + 'foo');
foo.doSomething('Hello', function(result){ console.log(result); });
Jedz w Joes
źródło
pytanie brzmi: „czy potrzebne są single”, a nie „jak je napisać”
mkoryak