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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nodejs中的单例模式 - 是否需要?相关的知识,希望对你有一定的参考价值。

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

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

因此,似乎每个必需的模块都可以很容易地用作单例,而不需要单独的样板代码。

题:

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

答案

这基本上与nodejs缓存有关。干净利落。

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

(在6.3.1中)

高速缓存

模块在第一次加载后进行缓存。这意味着(除其他外)每次调用require('foo')将获得完全相同的返回对象,如果它将解析为同一个文件。

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

如果要让模块多次执行代码,则导出一个函数,然后调用该函数。

模块缓存警告

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

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

所以简单来说。

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

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

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

另一答案

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

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

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

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

另一答案

当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");

而且,单身人士被打败了:

{ '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中使用单例模式。

另一答案

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

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

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

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

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

另一答案

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
另一答案

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

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

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

var singleton = function() {
    var socketList = {};
    return {
        add: function() {},
        ...
    };
}();
另一答案

这里唯一使用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方法来规避。

另一答案

单身人士在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关键字。任何旧的对象,功能,生命等都可以工作 - 这里没有OOP伏都教。

如果你在一个返回对它的引用的函数中关闭一个obj,并使该函数成为全局函数,那么奖励积分 - 那么即使重新分配全局变量也不会破坏已经从它创建的实例 - 虽然这是有用的。

另一答案

保持简单。

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); });

以上是关于nodejs中的单例模式 - 是否需要?的主要内容,如果未能解决你的问题,请参考以下文章

Python中的单例模式与反弹机制

你真的会写JAVA的单例模式吗?

线程安全的单例模式是否真的安全

python中的单例设计模式

Java中的单例模式

JAVA中的单例设计模式-分为懒汉式和饿汉式(附代码演示)