Паттерн Singleton в nodejs - это нужно?


Я недавно наткнулся на в этой статье о том, как написать синглтон в узел.js. Я знаю документацию requireгосударства что:

модули кэшируются после первой загрузки. Несколько звонков в require('foo') не может привести к многократному выполнению кода модуля.

таким образом, кажется, что каждый необходимый модуль может быть легко использован в качестве синглтона без синглтона шаблонный код.

вопрос:

обеспечивает ли приведенная выше статья раунд о решении для создания синглтона?

9 74

9 ответов:

Это в основном связано с кэшированием nodejs. Просто и понятно.

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

(v 6.3.1)

кэширование

модули кэшируются после первой загрузки. Это средство (среди прочего) что каждый вызов требовать ('foo') получит точно такой же объект возвращается, если он будет разрешать то же самое файл.

несколько вызовов require ('foo') может не вызывать код модуля выполняется несколько раз. Это важная особенность. С ним, "частично выполненные" объекты могут быть возвращены, что позволяет транзитивно зависимости, которые будут загружены, даже если они вызовут циклы.

Если вы хотите, чтобы модуль выполнял код несколько раз, то экспортируйте функцию и вызывать эту функцию.

Модуль Кэширования Предостережения

модули кэшируются на основе их разрешенного имени файла. Так как модули могут разрешить другим именем, в зависимости от местоположения вызывающего модуль (загрузка из папок node_modules), это не является гарантией того, что require ('foo') всегда будет возвращать тот же самый объект, если он будет разрешить в разные файлы.

кроме того, в файловых системах или операционных системах без учета регистра, различные разрешенные имена файлов могут указывать на один и тот же файл, но кэш по-прежнему будет рассматривать их как разные модули и перезагрузит файл несколько раз. Например, требовать ('.в /foo') и require('./FOO') возвращает два различных объекта, независимо от того, является ли или нет ./foo and ./FOO - это тот же файл.

Так что в простых терминах.

Если вы хотите Синглтон; экспортировать объект.

Если вы не хотите, одноэлементный; экспортировать функцию (и делать вещи/возвращать вещи/все, что в этой функции).

чтобы быть очень ясным, если вы это сделаете правильно он должен работать, Посмотрите на https://stackoverflow.com/a/33746703/1137669 (Ответ Аллена Люса). Это объясняет в коде, что происходит, когда кэширование терпит неудачу из-за по-разному разрешенных имен файлов. Но если вы всегда разрешаете одно и то же имя файла, оно должно работать.

все вышеперечисленное является чрезмерно сложным. Есть школа мысли, которая говорит, что шаблоны проектирования демонстрируют недостатки языка.

языки на базе прототипов объектно-ориентированного программирования (бесклассового) не нужен синглтон шаблон на всех. Вы просто создаете один (тонна) объект на лету, а затем использовать его.

Что касается модулей в узле, да, по умолчанию они кэшируются, но его можно настроить, например, если вы хотите горячую загрузку изменений модуля.

Но да, если вы хотите использовать общий объект по всему, помещая его в модуль экспорта нормально. Только не усложняйте его с помощью "singleton pattern", нет необходимости в нем в JavaScript.

синглтон в узле.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

глядя немного дальше на Модуль Кэширования Предостережения в модулях docs:

модули кэшируются на основе их разрешенного имени файла. Поскольку модули могут разрешаться в другое имя файла на основе местоположения вызывающего модуля (загрузка из папок node_modules),это не гарантия that require ('foo') всегда будет возвращать один и тот же объект, если он будет разрешен в разные файлы.

Так, в зависимости от где вы находитесь, когда вам требуется модуль, можно получить другой экземпляр модуля.

звучит как модули не простое решение для создания синглтонов.

Edit: или, может быть, они are. Как и @mkoryak, я не могу придумать случай, когда один файл может разрешать разные имена файлов (без использования символических ссылок). Но (как комментирует @JohnnyHK), несколько копий файла в разных node_modules каталоги каждый будет загружен и сохранен отдельно.

нет. когда происходит сбой кэширования модуля узла, этот одноэлементный шаблон терпит неудачу. Я изменил пример, чтобы работать осмысленно на 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.

вам не нужно ничего особенного, чтобы сделать синглтон в JS, код в статье могут быть:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

за пределами узла.js (например, в браузере js), вам нужно добавить функцию-оболочку вручную (это делается автоматически в узле.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()

единственная проблема здесь заключается в том, что вы не можете передать параметры в конструктор класса, но это можно обойти, вручную вызов init метод.

синглтоны хороши в 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 ключевое слово не требуются. Любой старый объект, функция, iife и т. д. будет работать - здесь не происходит ООП вуду.

бонусные очки если вы закрываете некоторый объект внутри функции, которая возвращает ссылку на него, и сделать эту функцию глобальной - то даже переназначение глобальной переменной не будет забивать экземпляры, уже созданные из него - хотя это сомнительно полезно.

сохраняя при этом его простым.

фу.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); });