Статьи

Шаблоны проектирования JavaScript: Синглтон

В этой статье мы рассмотрим лучший способ реализации синглтона в JavaScript и рассмотрим, как это развивалось с появлением ES6.

Среди языков, используемых в широко распространенном производстве, JavaScript, безусловно, наиболее быстро развивается, он выглядит не так, как его самые ранние итерации и больше похож на Python, с каждой новой спецификацией, выдвигаемой ECMA International. Несмотря на то, что изменения имеют свою значительную долю недоброжелателей, новый JavaScript действительно делает код более легким для чтения и рассуждений, более простым для написания таким образом, чтобы соответствовать передовым методам разработки программного обеспечения (особенно концепциям модульности и принципам SOLID), и проще собрать в канонические шаблоны проектирования программного обеспечения.

Объясняя ES6

ES6 (он же ES2015) был первым крупным обновлением языка с тех пор, как ES5 была стандартизирована в 2009 году. Почти все современные браузеры поддерживают ES6 . Однако, если вам нужно приспособить старые браузеры, код ES6 можно легко перенести в ES5 с помощью такого инструмента, как Babel. ES6 предоставляет JavaScript массу новых функций, включая улучшенный синтаксис для классов и новые ключевые слова для объявлений переменных . Вы можете узнать больше об этом, просматривая статьи SitePoint на эту тему .

Что такое синглтон

9 частей головоломки. 8 серые, 1 красный. Визуальное представление шаблона синглтона

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

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

Примечание: есть сценарии, когда синглеты могут быть плохими, и аргументы, что они, на самом деле, всегда плохие. Для этого обсуждения вы можете проверить эту полезную статью на эту тему.

Старый способ создания синглтона в JavaScript

Старый способ написания синглтона в JavaScript предполагает использование замыканий и немедленное использование выражений функций . Вот как мы можем написать (очень простое) хранилище для гипотетической реализации Flux по-старому:

var UserStore = (function(){
  var _data = [];

  function add(item){
    _data.push(item);
  }

  function get(id){
    return _data.find((d) => {
      return d.id === id;
    });
  }

  return {
    add: add,
    get: get
  };
}());

Когда этот код интерпретируется, UserStore

Однако этот код более многословен, чем должен быть, и также не дает нам неизменности, которую мы желаем при использовании синглетонов. Код, выполненный позже, может изменить любую из представленных функций или даже полностью переопределить UserStore Более того, модифицирующий / оскорбляющий код может быть где угодно! Если мы получили ошибки в результате неожиданной модификации UsersStore

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

Новый путь

Используя возможности ES6, главным образом модули и новое объявление переменных const

Давайте начнем с самой базовой реализации. Вот (более чистая и мощная) современная интерпретация приведенного выше примера:

 const _data = [];

const UserStore = {
  add: item => _data.push(item),
  get: id => _data.find(d => d.id === id)
}

Object.freeze(UserStore);
export default UserStore;

Как видите, этот способ улучшает читаемость. Но где он действительно сияет, так это в ограничении, накладываемом на код, который использует наш маленький одноэлементный модуль: потребляющий код не может переназначить UserStoreconst И в результате нашего использования Object.freeze его методы не могут быть изменены, и новые методы или свойства не могут быть добавлены к нему. Кроме того, поскольку мы используем преимущества модулей ES6, мы точно знаем, где используется UserStore

Теперь мы сделали UserStore В большинстве случаев использование литерала объекта является наиболее читабельным и лаконичным вариантом. Однако бывают случаи, когда вы захотите воспользоваться преимуществами традиционного занятия. Например, магазины во Flux будут иметь одинаковую базовую функциональность. Использование традиционного объектно-ориентированного наследования — один из способов получить эту повторяющуюся функциональность, сохраняя при этом ваш код СУХИМЫМ.

Вот как будет выглядеть реализация, если мы хотим использовать классы ES6:

 class UserStore {
  constructor(){
    this._data = [];
  }

  add(item){
    this._data.push(item);
  }

  get(id){
    return this._data.find(d => d.id === id);
  }
}

const instance = new UserStore();
Object.freeze(instance);

export default instance;

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

Одно из преимуществ маршрута класса, которое может быть неочевидным, состоит в том, что, если это ваш интерфейсный код, а ваш серверный написан на C # или Java, вы можете использовать множество одинаковых шаблонов проектирования в своем клиентском приложении. как вы делаете на бэкэнде, и повышаете эффективность вашей команды (если вы маленький, а люди работают на полную ставку). Звучит мягко и трудно поддается измерению, но я сам испытал это, работая над приложением C # с React-интерфейсом, и выгода была реальной.

Следует отметить, что с технической точки зрения мотивированный провокатор может подорвать неизменность и неопределяемость синглтона с использованием обоих этих шаблонов. Литерал объекта может быть скопирован, даже если он сам является константой, используя Object.assign . И когда мы экспортируем экземпляр класса, хотя мы непосредственно не подвергаем сам класс потребляющему коду, конструктор любого экземпляра доступен в JavaScript и может быть вызван для создания новых экземпляров. Очевидно, однако, что все это требует, по крайней мере, небольшого усилия, и, надеюсь, ваши коллеги-разработчики не так настаивают на нарушении схемы синглтона.

Но, скажем, вы хотели быть уверены, что никто не напутал с единственностью вашего синглтона, и вы также хотели, чтобы он еще более точно соответствовал реализации синглетонов в объектно-ориентированном мире. Вот что вы можете сделать:

 const

Добавив дополнительный этап удержания ссылки на экземпляр, мы можем проверить, class UserStore {
constructor(){
if(! UserStore.instance){
this._data = [];
UserStore.instance = this;
}

return UserStore.instance;
}

//rest is the same code as preceding example

}

const instance = new UserStore();
Object.freeze(instance);

export default instance;
UserStore Как вы можете видеть, это также хорошо использует тот факт, что мы сделали UserStore

Мысли? Ненавижу почту?

Нет сомнений в том, что многие разработчики уже несколько лет используют старый шаблон singleton / module в JavaScript и считают, что он вполне им подходит. Тем не менее, поскольку поиск лучших способов сделать что-то является центральным в духе того, чтобы быть разработчиком, мы надеемся, что мы увидим более чистые и легкие для рассуждения шаблоны, подобные этому, приобретающие все большую популярность. Особенно, когда становится проще и более распространенным использовать функции ES6 +.

Это шаблон, который я использовал на производстве, чтобы построить магазины в пользовательской реализации Flux (магазины, в которых было немного больше, чем в наших примерах), и это сработало хорошо. Но если вы видите дыры в нем, пожалуйста, дайте мне знать. Также, пожалуйста, отстаивайте любой из новых шаблонов, который вы предпочитаете, и считаете ли вы, что объектные литералы являются подходящим способом, или если вы предпочитаете классы!