web-dev-qa-db-ja.com

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

最近、Node.jsでシングルトンを記述する方法について この記事 に出会いました。 requirestates のドキュメントを知っています:

モジュールは、最初にロードされた後にキャッシュされます。 require('foo')を複数回呼び出しても、モジュールコードが複数回実行されることはありません。

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

質問:

上記の記事は、シングルトンを作成するための解決策について説明していますか?

84
mkoryak

これは基本的に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の答え)を参照してください。ファイル名の解決が異なるためにキャッシュが失敗した場合にコードで説明します。ただし、常に同じファイル名に解決する場合は機能します。

39
Karl Morrison

上記のすべては複雑すぎます。設計パターンは実際の言語の欠陥を示しているという考え方があります。

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

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

しかし、はい、共有オブジェクトを全面的に使用したい場合は、モジュールエクスポートにそれを置くことは問題ありません。 「シングルトンパターン」で複雑にしないでください。JavaScriptでは必要ありません。

134
user1046334

No。 Nodeのモジュールキャッシュが失敗すると、そのシングルトンパターンは失敗します。 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

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

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でシングルトンパターンを使用する必要性を感じたこともありません。

23
Allen Luce

Modulesドキュメントの Module Caching Caveats をもう少し見てください:

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

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

モジュールのような音はnotシングルトンを作成する簡単な解決策です。

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

20
mike

そのような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
18
Paul Armstrong

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

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

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

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

ここで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()

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

7
danday74

シングルトンは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キーワードの使用はnotであることに注意することが重要です。古いオブジェクト、関数、iifeなどはすべて機能します-ここでOOPブードゥー教は発生しません。

参照を返す関数内のobjを閉じて、その関数をグローバルにすると、ボーナス変数は、グローバル変数の再割り当てでさえ、それから既に作成されたインスタンスを上書きしません-これは疑いなく有用です。

4
Adam Tolley

シンプルに。

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); });
1
Eat at Joes