Шаблон Singleton в 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

(Версия 6.3.1)

Кеширование

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

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

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

Предупреждение о кешировании модуля

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

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

Итак, простыми словами.

Если вам нужен синглтон; экспортировать объект .

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

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

Обновление 2016

создание настоящего синглтона в node.js с символами es6 Другое решение : по этой ссылке

Обновление 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 05

1
Это своего рода плохая идея - по многим причинам, указанным в другом месте на этой странице, - но концепция по существу действительна, то есть при установленных номинальных обстоятельствах утверждения в этом ответе верны. Если вам нужен быстрый и грязный синглтон, это, скорее всего, сработает - просто не запускайте шаттлы с кодом.
Адам Толли

@AdamTolley "по многим причинам, указанным в другом месте на этой странице", вы имеете в виду файлы с символическими ссылками или неправильное написание имен файлов, которые, по-видимому, не используют один и тот же кеш? В документации говорится о файловых системах и операционных системах без учета регистра. Что касается символических ссылок, вы можете узнать больше здесь, поскольку это обсуждалось github.com/nodejs/node/issues/3402 . Также, если вы создаете символические ссылки или не понимаете свою ОС и узел должным образом, вам не следует находиться рядом с индустрией аэрокосмической техники;), однако я понимаю вашу точку зрения ^^.
basickarl

@KarlMorrison - просто потому, что документация не гарантирует этого, факт, что это кажется неопределенным поведением, или любая другая рациональная причина не доверять этому конкретному поведению языка. Может быть, кеш работает по-другому в другой реализации, или вам нравится работать в REPL и полностью разрушить функцию кеширования. Я хочу сказать, что кеш - это деталь реализации, и его использование в качестве одноэлементного эквивалента - это хитрый прием. Я люблю хитрые хаки, но их нужно различать, вот и все - (также никто не запускает шаттлы с узлом, я был глупым)
Адам Толли

136

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

Языки с ООП на основе прототипов (бесклассовые) вообще не нуждаются в одноэлементном шаблоне. Вы просто создаете один объект (тонну) на лету, а затем используете его.

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

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


27
Странно, что никто не получает голоса за ... есть +1 заThere is a school of thought which says design patterns are showing deficiencies of actual language.
Esailija 01

64
Синглтоны - это не антипаттерн.
wprl

5
@herby, похоже, является чрезмерно конкретным (и, следовательно, неправильным) определением шаблона singleton.
wprl

20
В документации говорится: «Множественные вызовы require ('foo') не могут вызывать многократное выполнение кода модуля». В нем написано «не может», но не «не будет», поэтому вопрос, как убедиться, что экземпляр модуля создается только один раз в приложении, с моей точки зрения, является допустимым.
xorcus

9
Заблуждение, что это правильный ответ на этот вопрос. Как отметил @mike ниже, возможно, что модуль загружается более одного раза, и у вас есть два экземпляра. Я сталкиваюсь с проблемой, когда у меня есть только одна копия Knockout, но создаются два экземпляра, потому что модуль загружается дважды.
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

Затем измените sg2строку require на:

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

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


21

Заглянем немного дальше в предупреждения о кешировании модулей в документации по модулям:

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

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

Похоже, модули - не простое решение для создания синглтонов.

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


Хорошо, я прочитал это 3 раза, и я все еще не могу вспомнить пример, в котором он будет разрешаться в другое имя файла. Помогите?
mkoryak 01

1
@mkoryak Я думаю, это относится к случаям, когда у вас есть два разных модуля, которые вам требуются, node_modulesкаждый из которых зависит от одного и того же модуля, но есть отдельные копии этого зависимого модуля в node_modulesподкаталоге каждого из двух разных модулей.
JohnnyHK 01

@mike, вы здесь, что модуль создается несколько раз при обращении по разным путям. Попался случай при написании юнит-тестов для серверных модулей. Мне нужен экземпляр singleton. как этого добиться?
Sushil

Примером могут быть относительные пути. например. Данный require('./db')код находится в двух отдельных файлах, код dbмодуля выполняется дважды
сценарий написан

9
У меня только что возникла неприятная ошибка, так как система узловых модулей нечувствительна к регистру. Я звонил require('../lib/myModule.js');в один файл и require('../lib/mymodule.js');в другой, и он не доставил один и тот же объект.
Heyarne

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 будет сброшен на пустой список
Джонатан.

3
@Джонатан. Контекст в документации вокруг этой цитаты, кажется, дает довольно убедительный аргумент, который, возможно, не используется в стиле RFC, НЕ ДОЛЖЕН .
Майкл

6
@Michael "может" - такое забавное слово. Представьте себе слово, которое в отрицании означает либо «возможно, нет», либо «определенно нет» ..
OJFord

1
may notПрименяется при npm linkдругих модулей в процессе разработки. Поэтому будьте осторожны при использовании модулей, которые полагаются на один экземпляр, например на eventBus.
mediafreakch

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 Faisal Hameed

1
В Node.js просто потребуйте его в другом файле ... const summary = require ('./ SummaryModule') ... и это будет тот же экземпляр. Вы можете проверить это, создав переменную-член и установив ее значение в одном файле, для которого она требуется, а затем получив ее значение в другом файле, для которого это требуется. Это должно быть то значение, которое было установлено.
danday74

9

Для синглтона в js ничего особенного не нужно, код в статье с таким же успехом может быть:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

Вне node.js (например, в браузере js) вам нужно добавить функцию-оболочку вручную (в node.js это делается автоматически):

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

Как указывает @Allen Luce, если кеширование узла не удается, шаблон singleton также не работает.
rakeen

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 и т. Д. Будут работать - здесь нет ООП-вуду.

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


вам это не нужно. вы можете просто сделать, module.exports = new Foo()потому что module.exports не будет выполняться снова, если вы не сделаете что-то действительно глупое
mkoryak 07

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

Вышеупомянутый ответ также был неправильным пониманием исходного вопроса: «Следует ли мне использовать синглтоны в JS или язык делает их ненужными?», Что, похоже, также является проблемой для многих других ответов. Я поддерживаю свою рекомендацию не использовать реализацию require в качестве замены правильной, явной реализации singleton.
Адам Толли

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.