nodejs中的单例模式 - 是否需要?

时间:2012-11-01 14:23:36

标签: javascript node.js design-patterns singleton

我最近遇到this article关于如何在Node.js中编写单例的问题。我知道require states的文档:

  

模块在第一次加载后进行缓存。多次调用require('foo')可能不会导致模块代码多次执行。

所以似乎每个必需的模块都可以很容易地用作没有单例样板代码的单例。

问题:

上面的文章是否提供了关于创建单例的解决方案?

9 个答案:

答案 0 :(得分:133)

以上所有内容都过于复杂。有一种思想流派认为设计模式显示出实际语言的缺陷。

基于原型的OOP(无类别)语言根本不需要单例模式。您只需动态创建一个(吨)对象,然后使用它。

对于node中的模块,是的,默认情况下它们是缓存的,但是如果你想热插入模块更改,可以调整它。

但是,是的,如果你想全部使用共享对象,将其放入模块导出就可以了。只是不要将它与“单例模式”复杂化,在JavaScript中不需要它。

答案 1 :(得分:36)

这与nodejs缓存基本相关。简单明了。

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

(v 6.3.1)

  

缓存

     

模块在第一次加载后进行缓存。这意味着   (除此之外)每个要求的呼叫(' foo')都会得到   返回完全相同的对象,如果它将解析为相同   文件。

     

多次调用require(' foo')可能不会导致模块代码   多次执行。这是一个重要的特征。用它,   "部分完成"可以返回对象,从而允许传递   即使它们会导致循环,也要加载依赖项。

     

如果您想让模块多次执行代码,那么导出   一个函数,并调用该函数。

     

模块缓存警告

     

模块根据其解析的文件名进行缓存。由于模块可能   根据调用的位置解析为不同的文件名   模块(从node_modules文件夹加载),它不能保证   require(' foo')将始终返回完全相同的对象,如果可以的话   解析为不同的文件。

     

此外,在不区分大小写的文件系统或操作系统上,   不同的已解析文件名可以指向同一个文件,但缓存   仍将它们视为不同的模块,并将重新加载该文件   多次。例如,要求(' ./ foo')并要求(' ./ FOO')   返回两个不同的对象,无论是否./foo和   ./FOO是同一个文件。

所以简单来说就是这样。

如果你想要一个单身人士; 导出对象

如果你不想要Singleton; 导出一个函数(并在该函数中执行东西/返回东西/任何东西)。

非常清楚,如果你这样做,它应该有效,请看https://stackoverflow.com/a/33746703/1137669(Allen Luce的回答)。它在代码中解释了当缓存由于不同解析的文件名而失败时会发生什么。但如果你总是解析相同的文件名它应该工作。

答案 2 :(得分:20)

进一步了解模块文档中的Module Caching Caveats

  

模块根据其解析的文件名进行缓存。由于模块可能会根据调用模块的位置(从node_modules文件夹加载)解析为不同的文件名,不保证需要('foo')将始终返回完全相同的对象,如果它会解析为不同的文件。

因此,根据您在需要模块时的位置,可以获得模块的不同实例。

听起来模块是创建单例的简单解决方案。

编辑或者他们 。像@mkoryak一样,我无法想出一个单个文件可能解析为不同文件名的情况(不使用符号链接)。但是(正如@JohnnyHK评论),不同node_modules目录中的文件的多个副本将分别加载和存储。

答案 3 :(得分:20)

否。当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需求行更改为:

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

bam ,单身人士被击败了:

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

我不知道一种可以接受的解决方法。如果您真的觉得需要制作类似单身的东西并且可以污染全局命名空间(以及可能导致的许多问题),您可以将作者的getInstance()exports行更改为:< / p>

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

module.exports = singleton.getInstance();

那就是说,我从来没有遇到生产系统的情况,我需要做这样的事情。我也从未觉得需要在Javascript中使用单例模式。

答案 4 :(得分: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 :(得分:9)

在js中你不需要做任何特殊的单例,文章中的代码也可以是:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

在node.js之外(例如,在浏览器js中),您需要手动添加包装函数(它在node.js中自动完成):

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

答案 6 :(得分:7)

此处唯一使用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 :(得分:4)

单身者在JS中很好,他们不需要如此冗长。

在节点中,如果需要单例,例如在服务器层中的各种文件中使用相同的ORM / DB实例,则可以将引用填充到全局变量中。

只要编写一个创建全局var的模块,如果它不存在,则返回对它的引用。

@ allen-luce正确地将他的脚注代码示例复制到这里:

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

module.exports = singleton.getInstance();

但请务必注意,使用new关键字是必需的。任何旧的对象,功能,生命等都可以工作 - 这里没有OOP伏都教。

如果你在一个返回对它的引用的函数中关闭一个obj,并使该函数成为全局函数,那么

奖励积分 - 然后即使重新分配全局变量也不会破坏已经从它创建的实例 - 尽管这样是有用的。

答案 8 :(得分:1)

保持简单。

foo.js

component

然后只是

function foo() {

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

  return bar;
};

module.exports = foo();