Nodejs'deki singleton kalıbı - gerekli mi?


97

Son zamanlarda Node.js'de bir singleton yazmanın nasıl yapılacağına dair bu makaleye rastladım. Şu require durumların belgelerini biliyorum :

Modüller ilk yüklendikten sonra önbelleğe alınır. Birden çok çağrı require('foo'), modül kodunun birden çok kez çalıştırılmasına neden olmayabilir.

Bu nedenle, gerekli olan her modül, singleton boilerplate-code olmadan kolayca singleton olarak kullanılabilir gibi görünüyor.

Soru:

Yukarıdaki makale, bir singleton oluşturmak için bir yuvarlak çözüm sağlıyor mu?


1
İşte 5 dk. bu konuyla ilgili açıklama (v6 ve npm3'ten sonra yazılmıştır): medium.com/@lazlojuly/…
lazlojuly

Yanıtlar:


60

Bunun temelde nodejs önbelleğe alma ile ilgisi var. Sade ve basit.

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

(v 6.3.1)

Önbelleğe almak

Modüller ilk yüklendikten sonra önbelleğe alınır. Bu, (diğer şeylerin yanı sıra), her gereksinim çağrısının ('foo'), aynı dosyaya çözümlenmesi durumunda, döndürülen nesnenin tam olarak aynısını alacağı anlamına gelir.

Zorunlu kılmak için birden çok çağrı ('foo'), modül kodunun birden çok kez çalıştırılmasına neden olmayabilir. Bu önemli bir özelliktir. Bununla, "kısmen tamamlanmış" nesneler döndürülebilir, böylece geçişli bağımlılıkların döngüye neden olsalar bile yüklenmesine izin verilir.

Bir modülün kodu birden çok kez yürütmesini istiyorsanız, bir işlevi dışa aktarın ve bu işlevi çağırın.

Modül Önbelleğe Alma Uyarıları

Modüller, çözümlenmiş dosya adlarına göre önbelleğe alınır. Modüller, çağıran modülün konumuna bağlı olarak farklı bir dosya adına çözümlenebileceğinden (node_modules klasörlerinden yükleme), gerekli ('foo'), farklı dosyalara çözümlenirse her zaman tam olarak aynı nesneyi döndürecek bir garanti değildir. .

Ek olarak, büyük / küçük harfe duyarlı olmayan dosya sistemlerinde veya işletim sistemlerinde, farklı çözümlenmiş dosya adları aynı dosyayı işaret edebilir, ancak önbellek yine de bunları farklı modüller olarak değerlendirecek ve dosyayı birden çok kez yeniden yükleyecektir. Örneğin, require ('./ foo') ve require ('./ FOO'), ./foo ve ./FOO'nun aynı dosya olup olmadığına bakılmaksızın iki farklı nesne döndürür.

Yani basit terimlerle.

Singleton isterseniz; bir nesneyi dışa aktarın .

Singleton istemiyorsanız; bir işlevi dışa aktar (ve bir şeyler yap / bir şeyler döndür / o işlevde ne varsa).

ÇOK net olmak gerekirse, eğer bunu doğru bir şekilde yaparsanız işe yaramalı , https://stackoverflow.com/a/33746703/1137669 (Allen Luce'nin cevabı) sayfasına bakın. Farklı çözümlenmiş dosya adları nedeniyle önbelleğe alma işlemi başarısız olduğunda ne olduğunu kodda açıklar. Ancak HER ZAMAN aynı dosya adına karar verirseniz, işe yaramalıdır.

2016 Güncellemesi

node.js'de es6 sembolleri ile gerçek bir singleton oluşturma Başka bir çözüm : bu bağlantıda

2020 Güncellemesi

Bu cevap, CommonJS ile ilgilidir ( Node.js'in modülleri içe / dışa aktarmak için kendi yolu). Node.js büyük olasılıkla ECMAScript Modüllerine geçecektir : https://nodejs.org/api/esm.html (ECMAScript, bilmiyorsanız JavaScript'in gerçek adıdır)

ECMAScript'e geçerken şimdilik aşağıdakileri okuyun: https://nodejs.org/api/esm.html#esm_writing_dual_packages_ while_avoiding_or_minimizing_hazards


3
Singleton isterseniz; teşekkür yardımcı olan bir nesneyi dışa aktar
danday74

1
Bu, bu sayfada başka bir yerde verilen birçok nedenden ötürü kötü bir fikirdir, ancak kavram esasen geçerlidir, yani yerleşik nominal koşullar altında, bu cevaptaki iddialar doğrudur. Hızlı ve kirli bir singleton istiyorsanız, bu muhtemelen işe yarayacaktır - sadece kodla herhangi bir servis başlatmayın.
Adam Tolley

@AdamTolley "Bu sayfanın başka bir yerinde verilen birçok nedenden dolayı", dosyaları sembolik bağlayan veya aynı önbelleği kullanmadığı anlaşılan dosya adlarının yanlış yazılmasından mı bahsediyorsunuz? Büyük / küçük harfe duyarlı olmayan dosya sistemleri veya işletim sistemleriyle ilgili sorunu belgelerde belirtir. Sembolik bağlantı ile ilgili olarak, burada tartışıldığı üzere daha fazlasını okuyabilirsiniz github.com/nodejs/node/issues/3402 . Ayrıca, dosyaları sembolik bağlıyorsanız veya işletim sisteminizi ve düğümünüzü doğru bir şekilde anlamıyorsanız, havacılık ve uzay mühendisliği endüstrisine yakın bir yerde olmamalısınız;), yine de amacınızı anlıyorum ^^.
basickarl

@KarlMorrison - sadece belgelerin bunu garanti etmemesi, belirtilmemiş bir davranış gibi görünmesi veya dilin bu özel davranışına güvenmemek için başka herhangi bir mantıklı neden. Belki önbellek başka bir uygulamada farklı çalışır veya REPL'lerde çalışmayı ve önbelleğe alma özelliğini tamamen altüst etmeyi seviyorsunuz. Demek istediğim, önbellek bir uygulama ayrıntısı ve bunun tekil eşdeğer olarak kullanılması akıllıca bir hack. Akıllı bilgisayar korsanlarını severim, ancak farklılaştırılmaları gerekir, hepsi bu - (ayrıca kimse düğümle servis fırlatmıyor, aptalca davranıyordum)
Adam Tolley

138

Yukarıdakilerin tümü aşırı karmaşıktır. Tasarım modellerinin gerçek dilin eksikliklerini gösterdiğini söyleyen bir düşünce okulu var.

Prototip tabanlı OOP'ye (sınıfsız) sahip diller, tek bir desene hiç ihtiyaç duymaz. Sadece anında tek bir (ton) nesne oluşturursunuz ve sonra onu kullanırsınız.

Düğümdeki modüllere gelince, evet, varsayılan olarak önbelleğe alınır, ancak örneğin modül değişikliklerinin çalışırken yüklenmesini istiyorsanız ince ayar yapılabilir.

Ama evet, paylaşılan nesneyi her yerde kullanmak istiyorsanız, onu bir modül dışa aktarımına koymak iyidir. Sadece "singleton pattern" ile karmaşıklaştırmayın, JavaScript'te buna gerek yok.


27
Kimsenin olumlu oy almaması garip ... için +1 alThere is a school of thought which says design patterns are showing deficiencies of actual language.
Esailija

64
Tekli tonlar bir anti-model değildir.
wprl

5
@herby, tekil modelin aşırı spesifik (ve bu nedenle yanlış) bir tanımı gibi görünüyor.
wprl

20
Dokümantasyon şu şekildedir: "Çok sayıda zorunlu çağrı ('foo') , modül kodunun birden çok kez çalıştırılmasına neden olmayabilir .". "Olmayabilir" diyor, "olmayacak" demiyor, bu nedenle modül örneğinin bir uygulamada yalnızca bir kez oluşturulduğundan nasıl emin olacağımı sormak benim açımdan geçerli bir sorudur.
xorcus

9
Bu soru için doğru cevabın bu olduğu yanıltıcıdır. @Mike'ın aşağıda işaret ettiği gibi, bir modülün birden fazla yüklenmesi ve iki örneğiniz olması mümkündür. Knockout'un yalnızca bir kopyasına sahip olduğum ancak modül iki kez yüklendiğinden iki örnek oluşturulduğu bu soruna ulaşıyorum.
dgaviola

26

Hayır . Düğümün modül önbelleğe alma işlemi başarısız olduğunda, bu tekli model başarısız olur. Örneği OSX'te anlamlı bir şekilde çalışacak şekilde değiştirdim:

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

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

Bu, yazarın beklediği çıktıyı verir:

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

Ancak küçük bir değişiklik önbelleğe almayı geçersiz kılar. OSX'te şunu yapın:

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

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

Veya Linux'ta:

% ln singleton.js singleton2.js

Ardından, sg2gerekli satırını şu şekilde değiştirin:

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

Ve bam , singleton yenildi:

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

Bunu aşmanın kabul edilebilir bir yolunu bilmiyorum. Eğer gerçekten tekil benzeri bir şey yapma ihtiyacı hissediyorsanız ve küresel ad alanını kirletmekte sorun yaşıyorsanız (ve ortaya çıkabilecek birçok sorunu), yazarın getInstance()ve exportssatırlarını şu şekilde değiştirebilirsiniz:

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

module.exports = singleton.getInstance();

Bununla birlikte, bir prodüksiyon sisteminde böyle bir şey yapmam gereken bir duruma hiç rastlamadım. Ayrıca Javascript'te tekli kalıbı kullanma ihtiyacı hissetmedim.


21

Modüller belgelerindeki Modül Önbelleğe Alma Uyarılarına biraz daha yakından bakın:

Modüller, çözümlenmiş dosya adlarına göre önbelleğe alınır. Modüller, çağıran modülün konumuna bağlı olarak farklı bir dosya adına çözümlenebileceğinden (node_modules klasörlerinden yükleme), gerekli ('foo'), farklı dosyalara çözümlenirse her zaman tam olarak aynı nesneyi döndürecek bir garanti değildir. .

Dolayısıyla, bir modüle ihtiyaç duyduğunuzda nerede olduğunuza bağlı olarak, modülün farklı bir örneğini almak mümkündür.

Modüller gibi sesler, tekil oluşturmak için basit bir çözüm değildir .

Düzenleme: Ya da belki de vardır . @Mkoryak gibi, tek bir dosyanın farklı dosya adlarına (sembolik bağlantılar kullanmadan) çözümlenebileceği bir durum ortaya koyamıyorum. Ancak (@JohnnyHK yorumunda olduğu gibi), bir dosyanın farklı node_modulesdizinlerdeki birden fazla kopyası her biri ayrı ayrı yüklenecek ve saklanacaktır.


Tamam, bunu 3 kez okudum ve hala farklı bir dosya adına çözümleneceği bir örnek üzerinde düşünemiyorum. Yardım?
mkoryak

1
@mkoryak Sanırım bu, ihtiyaç duyduğunuz iki farklı modülün node_modulesolduğu ve her birinin aynı modüle bağlı olduğu durumlara atıfta bulunuyor , ancak node_modulesiki farklı modülün her birinin alt dizini altında bu bağımlı modülün ayrı kopyaları var .
JohnnyHK

@mike, farklı yollardan başvurulduğunda bu modül birden çok kez somutlaştırılır. Sunucu modülleri için birim testleri yazarken duruma çarptım. Bir çeşit singleton örneğine ihtiyacım var. nasıl başarılır?
Sushil

Bir örnek göreceli yollar olabilir. Örneğin. Verilen require('./db')iki ayrı dosyada, dbmodülün kodu iki kez çalıştırılır
2013 tarihinde

9
Düğüm modülü sistemi durum-insenstivie olduğu için kötü bir hata yaşadım. require('../lib/myModule.js');bir dosyada ve diğerinde aradım require('../lib/mymodule.js');ve aynı nesneyi teslim etmedi.
heyarne

19

Node.js'de (veya bu konuda tarayıcı JS'de) bunun gibi bir singleton tamamen gereksizdir.

Modüller önbelleğe alındığından ve durum bilgisi olduğundan, sağladığınız bağlantıda verilen örnek çok daha basit bir şekilde kolayca yeniden yazılabilir:

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

5
Dokümanlar, " modül kodunun birden çok kez çalıştırılmasına neden olmayabilir " diyor, bu nedenle birden çok kez çağrılması olasıdır ve bu kod tekrar çalıştırılırsa, socketList boş bir listeye sıfırlanacaktır
Jonathan.

3
@Jonathan. Bağlam dokümanlarında bu teklif etrafında oldukça ikna edici bir dava yapmak görünmek olmayabilir RFC tarzı kullanılıyor olmamalıdır .
Michael

6
@Michael "may" böyle komik bir kelime.
Reddedildiğinde

1
may notGeçerlidir zaman npm linkgeliştirme sırasında diğer modüller. Bu nedenle, eventBus gibi tek bir örneğe dayanan modülleri kullanırken dikkatli olun.
mediafreakch

10

Js'de bir singleton yapmak için özel bir şeye ihtiyacınız yok, makaledeki kod şöyle olabilir:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

Node.js dışında (örneğin, tarayıcı js'de), sarmalayıcı işlevini manuel olarak eklemeniz gerekir (node.js'de otomatik olarak yapılır):

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

@Allen Luce tarafından belirtildiği gibi, düğümün önbelleğe alma işlemi başarısız olursa, tekil kalıp da başarısız olur.
2017

10

Buradaki tek cevap, ES6 sınıflarını kullanan

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

bu tekli şunlarla gerekli:

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

Buradaki tek sorun, sınıf yapıcısına params geçirememenizdir, ancak bu, bir inityöntemi manuel olarak çağırarak engellenebilir .


soru "tekil gerekli mi", "nasıl yazarsın" değil
mkoryak

@ danday74 Aynı örneği summary, yeniden başlatmadan başka bir sınıfta nasıl kullanabiliriz ?
M Faisal Hameed

1
Node.js'de sadece başka bir dosyada gerektirir ... const özet = gerektirir ('./ ÖzetModule') ... ve aynı örnek olacaktır. Bunu, bir üye değişkeni oluşturarak ve değerini gerektiren bir dosyada ayarlayarak ve ardından onu gerektiren başka bir dosyada değerini alarak test edebilirsiniz. Ayarlanan değer olmalıdır.
danday74

6

Singleton'lar JS'de iyidir, sadece bu kadar ayrıntılı olmaları gerekmez.

Düğümde, örneğin sunucu katmanınızdaki çeşitli dosyalarda aynı ORM / DB örneğini kullanmak için bir tekil seçeneğe ihtiyacınız varsa, referansı bir global değişkene doldurabilirsiniz.

Eğer mevcut değilse, global değişkeni yaratan bir modül yazın, sonra buna bir referans verir.

@ allen-luce, burada kopyaladığı dipnot kodu örneğiyle haklıydı:

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

module.exports = singleton.getInstance();

ancak newanahtar kelimeyi kullanmanın gerekli olmadığına dikkat etmek önemlidir . Herhangi bir eski nesne, işlev, yaşam vb. Çalışacaktır - burada OOP vudu gerçekleşmiyor.

Bir işlevin içinde kendisine referans döndüren bir nesneyi kapatırsanız ve bu işlevi global yaparsanız bonus puan kazanır - bu durumda global değişkenin yeniden atanması bile ondan zaten oluşturulmuş örnekleri bozmaz - ancak bu şüpheli derecede yararlıdır.


bunların hiçbirine ihtiyacın yok. Sadece yapabilirsiniz module.exports = new Foo()module.exports tekrar çalıştırmak çünkü gerçekten aptalca bir şey yapmadıkça,
mkoryak

Uygulamanın yan etkilerine kesinlikle güvenmemelisiniz. Tek bir örneğe ihtiyacınız varsa, uygulamanın değişmesi durumunda onu bir global'e bağlayın.
Adam Tolley

Yukarıdaki cevap aynı zamanda 'JS'de singleton kullanmalı mıyım yoksa dil onları gereksiz kılıyor mu?' Şeklindeki orijinal sorunun yanlış anlaşılmasıydı, bu da diğer birçok cevapla ilgili bir sorun gibi görünüyor. Gereksinim uygulamasını uygun, açık bir tekil uygulama yerine kullanmak üzere kullanmaya karşı tavsiyemin arkasında duruyorum.
Adam Tolley

1

Basit tutmak.

foo.js

function foo() {

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

  return bar;
};

module.exports = foo();

O zaman sadece

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

soru "tekil gerekli mi", "nasıl yazarsın" değil
mkoryak
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.