nodejsのシングルトンパターン-必要ですか?


95

私は最近、Node.jsでシングルトンを作成する方法についてこの記事を見つけました。私はrequire 次のような州の文書を知っています:

モジュールは、最初にロードされた後にキャッシュされます。への複数の呼び出しrequire('foo')により、モジュールコードが複数回実行されない場合があります。

したがって、必要なすべてのモジュールは、シングルトンボイラープレートコードなしでシングルトンとして簡単に使用できるようです。

質問:

上記の記事は、シングルトンを作成するためのラウンドアラウンドソリューションを提供していますか?


1
これは5分です。(V6とnpm3後に書かれた)このトピックに関する説明:medium.com/@lazlojuly/...
lazlojuly

回答:


56

これは基本的にnodejsキャッシングに関係しています。簡潔でシンプル。

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

(v 6.3.1)

キャッシング

モジュールは、最初にロードされた後にキャッシュされます。つまり、(特に)require( 'foo')を呼び出すたびに、同じファイルに解決される場合は、まったく同じオブジェクトが返されます。

require( 'foo')を複数回呼び出しても、モジュールコードが複数回実行されることはありません。これは重要な機能です。これにより、「部分的に完了した」オブジェクトを返すことができるため、循環が発生する場合でも推移的な依存関係をロードできます。

モジュールでコードを複数回実行する場合は、関数をエクスポートして、その関数を呼び出します。

モジュールのキャッシングに関する警告

モジュールは、解決されたファイル名に基づいてキャッシュされます。モジュールは、(node_modulesフォルダーからロードする)呼び出しモジュールの場所に基づいて異なるファイル名に解決される可能性があるため、require( 'foo')が異なるファイルに解決する場合、常にまったく同じオブジェクトを返す保証はありません。

さらに、大文字と小文字を区別しないファイルシステムまたはオペレーティングシステムでは、解決された異なるファイル名が同じファイルを指す可能性がありますが、キャッシュはそれらを異なるモジュールとして扱い、ファイルを複数回リロードします。たとえば、require( './ foo')とrequire( './ FOO')は、。/ fooと./FOOが同じファイルであるかどうかに関係なく、2つの異なるオブジェクトを返します。

簡単に言えば。

シングルトンが必要な場合; オブジェクトをエクスポートします

シングルトンが必要ない場合。関数をエクスポートします(そして、その関数で何かを行う/戻す/何かを行います)。

非常に明確にするために、これを適切に実行すれば機能するはずです。https://stackoverflow.com/a/33746703/1137669(Allen Luceの回答)をご覧ください。ファイル名の解決方法が異なるためにキャッシュが失敗した場合に何が起こるかをコードで説明します。ただし、常に同じファイル名に解決する場合は機能します。

2016年の更新

es6シンボルを含むnode.jsで真のシングルトンを作成する 別の解決策このリンク

アップデート2020

この回答はCommonJS(Node.jsがモジュールをインポート/エクスポートする独自の方法)を参照しています。Node.jsは、おそらくECMAScriptモジュールに切り替わりますhttps : //nodejs.org/api/esm.html (ECMAScriptは、ご存じない場合はJavaScriptの本当の名前です)

ECMAScriptに移行するときは、今のところ以下をお読みください。https//nodejs.org/api/esm.html#esm_writing_dual_packages_while_avoiding_or_minimizing_hazards


3
シングルトンが必要な場合; オブジェクトのエクスポート...おかげで感謝しました
danday74

1
これは悪い考えのようなものです-このページの他の場所で示されている多くの理由から- 概念は本質的に有効です。つまり、確立された名目上の状況下では、この回答の主張は真実です。速くて汚いシングルトンが必要な場合、これはおそらく機能します-コードでシャトルを起動しないでください。
Adam Tolley 2017

@AdamTolleyは、「このページの他の場所に示されている多くの理由から」、同じキャッシュを利用していないように見えるファイルのシンボリックリンクまたはスペルミスのファイル名を参照していますか?大文字と小文字を区別しないファイルシステムまたはオペレーティングシステムに関する問題がドキュメントに記載されています。シンボリックリンクについては、github.com/nodejs/node/issues/3402で説明したように、こちらをご覧ください。また、ファイルをシンボリックリンクしている場合や、OSとノードを正しく理解していない場合は、航空宇宙工学業界の近くにいるべきではありません;)、しかし、私はあなたの要点を理解しています^^。
K-SOの毒性が高まっています。

@KarlMorrison-ドキュメントがそれを保証しないという事実、それが不特定の動作であるように見えるという事実、または言語のこの特定の動作を信頼しないその他の合理的な理由。おそらく、キャッシュは別の実装では異なる動作をするか、REPLで作業してキャッシング機能を完全に破壊したい場合があります。私のポイントは、キャッシュは実装の詳細であり、シングルトンの同等物は巧妙なハックとして使用されることです。私は巧妙なハックが大好きですが、区別する必要があります。それだけです(ノードを使ってシャトルを起動している人はいません。ばかげていました)
Adam Tolley

136

上記のすべてが複雑すぎます。デザインパターンは実際の言語の欠陥を示していると言う思想の学校があります。

プロトタイプベースのOOP(クラスレス)を使用する言語では、シングルトンパターンはまったく必要ありません。その場でシングル(ton)オブジェクトを作成し、それを使用するだけです。

ノード内のモジュールについては、はい、デフォルトでキャッシュされますが、たとえば、モジュールの変更のホットロードが必要な場合は微調整できます。

しかし、はい、共有オブジェクトをすべて使用したい場合は、それをモジュールに入れてエクスポートします。"シングルトンパターン"で複雑にしないでください。JavaScriptでは必要ありません。


27
奇妙なことに誰も賛成票を獲得していません... +1をお持ちくださいThere is a school of thought which says design patterns are showing deficiencies of actual language.
エサイリア

64
シングルトンはアンチパターンではありません。
wprl 2013

4
@herbyは、シングルトンパターンの過度に具体的な(したがって正しくない)定義のようです。
wprl 2013年

19
ドキュメントには、「require( 'foo')を複数回呼び出すと、モジュールコードが複数回実行されない場合があります。」と記載されています。それは「そうでないかもしれない」、「そうしないだろう」というわけではないので、アプリケーションでモジュールインスタンスが一度だけ作成されることを確認する方法を尋ねることは私の観点からは有効な質問です。
xorcus 2014

9
これがこの質問に対する正しい答えであることは誤解を招くものです。@mikeが以下で指摘するように、モジュールが2回以上読み込まれ、2つのインスタンスがある可能性があります。Knockoutのコピーが1つしかありませんが、モジュールが2回読み込まれるために2つのインスタンスが作成されるという問題が発生しています。
dgaviola

26

いいえ。 ノードのモジュールキャッシュが失敗すると、そのシングルトンパターンは失敗します。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());

これにより、作成者が予想した出力が得られます。

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

しかし、小さな変更はキャッシングを無効にします。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());

または、Linuxの場合:

% ln singleton.js singleton2.js

次に、sg2require行を次のように変更します。

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

そして、bam、シングルトンは打ち負かされています:

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

私はこれを回避するための許容できる方法を知りません。シングルトンのようなものを作成する必要性を本当に感じており、グローバル名前空間を汚染することに問題がない場合(および結果として生じる可能性のある多くの問題)、作成者getInstance()exports行を次のように変更できます。

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

module.exports = singleton.getInstance();

とはいえ、このようなことをする必要がある運用システムの状況に遭遇したことはありません。また、JavaScriptでシングルトンパターンを使用する必要性を感じたことはありません。


21

モジュールのドキュメントでモジュールのキャッシュに関する警告をもう少し見てみましょう

モジュールは、解決されたファイル名に基づいてキャッシュされます。モジュールは、(node_modulesフォルダーからロードする)呼び出しモジュールの場所に基づいて異なるファイル名に解決される可能性があるため、require( 'foo')が異なるファイルに解決する場合、常にまったく同じオブジェクトを返す保証はありません

そのため、モジュールが必要な場所に応じて、モジュールの別のインスタンスを取得することが可能です。

モジュールのような音、シングルトンを作成するための単純なソリューションではありません

編集:または多分そうです。@mkoryakのように、単一のファイルが(シンボリックリンクを使用せずに)異なるファイル名に解決される場合は思いつきません。しかし(@JohnnyHKコメントとして)、異なるnode_modulesディレクトリにあるファイルの複数のコピーがそれぞれ個別にロードされて保存されます。


わかりました、私はそれを3回読みましたが、それでも別のファイル名に解決される例を考えることはできません。助けて?
mkoryak

1
@mkoryakこれは、必要な2つの異なるモジュールがnode_modulesあり、それぞれが同じモジュールに依存している場合を指していると思いますnode_modulesが、2つの異なるモジュールのそれぞれのサブディレクトリの下に、その依存モジュールの個別のコピーがあります。
JohnnyHK

@mikeあなたがここにいるのは、異なるパスから参照されたときに、モジュールが複数回インスタンス化されるためです。私は、サーバーモジュールの単体テストを作成するときに、問題にぶつかりました。シングルトンインスタンスが必要です。それを達成する方法?
スシル2013年

例としては、相対パスがあります。例えば。与えられたがrequire('./db')、二つの別々のファイルにするためのコードでdb二回モジュールの実行
willscripted

9
ノードモジュールシステムは大文字と小文字を区別しないため、厄介なバグが発生しました。require('../lib/myModule.js');1つのファイルとrequire('../lib/mymodule.js');別のファイルを呼び出しましたが、同じオブジェクトが配信されませんでした。
heyarne 2014年

18

このようなnode.js(またはブラウザのJSの場合)のシングルトンは完全に不要です。

モジュールはキャッシュされ、ステートフルであるため、提供したリンクに示されている例は、はるかに簡単に簡単に書き直すことができます。

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
ドキュメントは「モジュールコードが複数回実行されることはないかもしれない」と言っているため、複数回呼び出される可能性があり、このコードが再度実行されると、socketListは空のリストにリセットされます
Jonathan。

3
@ジョナサン。コンテキストドキュメントでその引用の周りにはかなり説得力のあるケース作るように見えないかもしれません RFCスタイルの中で使用されてはいけませんが
マイケル

6
@マイケル「メイ」はそのような面白い言葉です。否定されたときに「多分そうではない」または「間違いなく」のいずれかを意味するという言葉を持つ空想は..
OJFord

1
may not適用されたときにnpm link、開発中の他のモジュール。したがって、eventBusなどの単一のインスタンスに依存するモジュールを使用するときは注意してください。
mediafreakch 2016

10

ES6クラスを使用するここでの唯一の答え

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

このシングルトンが必要です:

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

ここでの唯一の問題は、パラメーターをクラスコンストラクターに渡すことができないが、initメソッドを手動で呼び出すことで回避できることです。


問題は「シングルトンが必要かどうか」であり、「どのように書くか」ではありません
mkoryak

@ danday74 summary再度初期化せずに、別のクラスで同じインスタンスを使用するにはどうすればよいですか?
Mファイサルハ

1
Node.jsでは、別のファイルでそれを要求するだけです... const summary = require( './ SummaryModule')...そして、それは同じインスタンスになります。これをテストするには、メンバー変数を作成し、それを必要とする1つのファイルにその値を設定してから、それを必要とする別のファイルにその値を取得します。設定された値でなければなりません。
danday74

9

jsでシングルトンを実行するために特別なことは必要ありません。記事のコードは次のようにすることもできます。

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

node.jsの外(たとえば、ブラウザーのjs)では、ラッパー関数を手動で追加する必要があります(node.jsで自動的に行われます)。

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

@Allen Luceが指摘したように、ノードのキャッシュが失敗すると、シングルトンパターンも失敗します。
レイクイーン

6

JSではシングルトンは問題ありませんが、それほど冗長である必要はありません。

ノードでシングルトンが必要な場合、たとえばサーバーレイヤーのさまざまなファイルで同じORM / DBインスタンスを使用する場合は、グローバル変数に参照を詰め込むことができます。

グローバル変数が存在しない場合にそれを作成するモジュールを記述し、それへの参照を返すだけです。

@ allen-luceは、脚注のコード例を次のようにコピーして、正しく説明しました。

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

module.exports = singleton.getInstance();

ただし、newキーワードの使用は必須ではないことに注意してください。古いオブジェクト、関数、iifeなどが機能します。ここではOOPブードゥーは発生しません。

参照を返す関数内のobjを閉じてその関数をグローバルにすると、ボーナスポイントが得られます。グローバル変数を再割り当てしても、そこからすでに作成されているインスタンスが上書きされることはありません。


あなたはそれを必要としません。あなたができるのはmodule.exports = new Foo()、本当に愚かなことをしない限り、module.exportsが再度実行されないためです
mkoryak

実装の副作用に絶対に依存すべきではありません。実装が変更された場合に備えて、単一のインスタンスが必要な場合は、それをグローバルに関連付けるだけです。
Adam Tolley 2016年

上記の回答は、「JSでシングルトンを使用する必要がありますか、または言語によってそれらが不要になるのですか?」という元の質問の誤解でもありました。これは、他の多くの回答でも問題のようです。私は、適切な明示的なシングルトン実装の代わりとしてrequire実装を使用しないことを推奨しています。
Adam Tolley、

1

シンプルに保つ。

foo.js

function foo() {

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

  return bar;
};

module.exports = foo();

じゃあ

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

問題は、「シングルトンが必要かどうか」であり、「どのように書くか」ではありません
mkoryak
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.