Преобразование объектов Singleton JS для использования классов ES6

Я использую ES6 с транспилером Webpack es6 согласно моей статье здесь: http://www.railsonmaui.com/blog/2014/10/02/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/

Имеет ли смысл преобразовывать два объекта Singleton для использования классов ES6?

import { CHANGE_EVENT } from "../constants/Constants";

var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/merge');

var _flash = null;

var BaseStore = merge(EventEmitter.prototype, {

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

  getFlash: function() {
    return _flash;
  },

  setFlash: function(flash) {
    _flash = flash;
  }
});

export { BaseStore };

Это файл ManagerProducts.jsx, в котором есть синглтон, который должен расширяться из BaseStore.

/**
 * Client side store of the manager_product resource
 */
import { BaseStore } from "./BaseStore";
import { AppDispatcher } from '../dispatcher/AppDispatcher';
import { ActionTypes } from '../constants/Constants';
import { WebAPIUtils } from '../utils/WebAPIUtils';
import { Util } from "../utils/Util";
var merge = require('react/lib/merge');

var _managerProducts = [];

var receiveAllDataError = function(action) {
  console.log("receiveAllDataError %j", action);
  WebAPIUtils.logAjaxError(action.xhr, action.status, action.err);
};

var ManagerProductStore = merge(BaseStore, {
  getAll: function() {
    return _managerProducts;
  }
});

var receiveAllDataSuccess = function(action) {
  _managerProducts = action.data.managerProducts;
  //ManagerProductStore.setFlash({ message: "Manager Product data loaded"});
};


ManagerProductStore.dispatchToken = AppDispatcher.register(function(payload) {
  var action = payload.action;
  if (Util.blank(action.type)) { throw `Invalid action, payload ${JSON.stringify(payload)}`; }

  switch(action.type) {
    case ActionTypes.RECEIVE_ALL_DATA_SUCCESS:
      receiveAllDataSuccess(action);
      break;
    case ActionTypes.RECEIVE_ALL_DATA_ERROR:
      receiveAllDataError(action);
      break;
    default:
      return true;
  }
  ManagerProductStore.emitChange();
  return true;
});

export { ManagerProductStore };

person justingordon    schedule 05.10.2014    source источник
comment
Нет. Если у вас синглтон, вам не нужен класс в JavaScript. Классы предназначены для создания нескольких экземпляров.   -  person Bergi    schedule 05.10.2014
comment
@bergi, я так и думал. Но просто проверяю, есть ли что-то, что я упускаю.   -  person justingordon    schedule 06.10.2014


Ответы (5)


Я бы сказал, что синглтоны (классы, которые управляют своим собственным временем жизни синглтона) не нужны ни в каком языке. Это не значит, что время жизни синглтона бесполезно, просто я предпочитаю, чтобы время жизни объекта управлялось чем-то другим, а не классом, например контейнером DI.

При этом шаблон singleton МОЖЕТ быть применен к классам JavaScript, заимствуя шаблон SingletonEnforcer, который использовался в ActionScript. Я вижу желание сделать что-то подобное при переносе существующей кодовой базы, использующей синглтоны, в ES6.

В этом случае идея состоит в том, что вы создаете закрытый (через открытый символ) статический singleton экземпляр с общедоступным статическим instance геттером. Затем вы ограничиваете конструктор чем-то, что имеет доступ к специальному символу singletonEnforcer, который не отображается за пределами модуля. Таким образом, конструктор выйдет из строя, если кто-либо, кроме синглтона, попытается его «обновить». Это будет выглядеть примерно так:

const singleton = Symbol();
const singletonEnforcer = Symbol()

class SingletonTest {

  constructor(enforcer) {
    if(enforcer != singletonEnforcer) throw "Cannot construct singleton";
  }

  static get instance() {
    if(!this[singleton]) {
      this[singleton] = new SingletonTest(singletonEnforcer);
    }
    return this[singleton];
  }
}

export default SingletonTest

Затем вы можете использовать его как любой другой синглтон:

import SingletonTest from 'singleton-test';
const instance = SingletonTest.instance;
person Brian Genisio    schedule 07.10.2014
comment
amanvirk.me/singleton-classes-in-es6 является лучшим подходом, так как он управлять состоянием внутри самого конструктора - person Aman Virk; 20.05.2015
comment
@AmanVirk Этот метод работает, если вы расширяете класс? - person jedd.ahyoung; 03.09.2015
comment
IMO, это немного странно использовать это в получателе экземпляра. Он не может ссылаться на сам класс, так как это статический метод. Я бы скорее поставил ``` const instance = null; ``` вне класса, а затем использовать instance в коде. Процессор все равно поместит все это в замыкание, так что в конце концов это будет частная переменная. Кроме того, это кажется хорошим решением! - person dejakob; 06.02.2016
comment
@dejakob Ссылка на this в статическом методе будет ссылаться на CLASS (точнее, на прототип), а не на объект. Используя символ, определенный вне класса, мы, по сути, создаем закрытый член класса (который отличается от закрытого экземпляра члена). Единственная причина сделать это — сохранить семантику класса. Однако я настоятельно предпочитаю не использовать синглтоны. Я предпочитаю, чтобы контейнер отвечал за всю жизнь. Мне не нравится, когда классы несут ответственность за свою жизнь. Это всегда кажется неправильным. - person Brian Genisio; 08.02.2016
comment
Почему не const singletonTest = Object.create({ }); export default singletonTest;? - person Darlesson; 10.02.2017
comment
@Darlesson Да, я думаю, что это то, что я бы сделал на практике. Это пример классического одноэлементного шаблона, в котором экспортируется сам класс, а экземпляр является свойством уровня класса. Я никогда не пишу код таким образом. - person Brian Genisio; 12.02.2017
comment
@AmanVirk это не сработает, если вы попытаетесь создать одиночный расширяющийся класс. - person shinzou; 25.04.2017

Нет. Не имеет смысла.

Вот действительно простой пример одноэлементного объекта в es6:

let appState = {};
export default appState;

Если вы действительно хотите использовать класс в своем одноэлементном подходе, я бы рекомендовал не использовать «статический», поскольку он более запутан, чем хорош для одноэлементного, по крайней мере, для JS, и вместо этого возвращает экземпляр класса как одноэлементный, например...

class SomeClassUsedOnlyAsASingleton {
  // implementation
}

export default new SomeClassUsedOnlyAsASingleton();

Таким образом, вы по-прежнему можете использовать все классы, которые вам нравятся, которые предлагает JavaScript, но это уменьшит путаницу, поскольку IMO static в любом случае не полностью поддерживается в классах JavaScript, как в типизированных языках, таких как C # или Java, поскольку он поддерживает только статические методы. если вы просто не подделываете его и не прикрепляете их непосредственно к классу (на момент написания этой статьи).

person King Friday    schedule 30.04.2015
comment
Или только одну строчку export default let appState = {}; :-) - person Bergi; 01.05.2015
comment
@Jason: Спасибо, ты действительно открыл мне глаза. Я также пытался определить одноэлементный класс, но иметь один объект намного проще. Поскольку вы даже можете определять функции в этом объекте, я не вижу никаких недостатков. - person waldgeist; 21.08.2015
comment
let appState = { func: function() { // бла-бла-бла } }; - person pyrsmk; 29.09.2015
comment
Правильно, но что вы собираетесь делать, если вам действительно нужны вещи определенного класса, например, расширение из другого класса. Конечно, вы могли бы использовать наследование прототипов старой школы и вернуть этот объект, но я думаю, что OP на самом деле предпочитает использовать классы ES2015 для этого. - person dejakob; 06.02.2016
comment
@dejakob: литерал es6 obj? Я уверен, что кто-то может указать причину не делать этого, но: export default { __proto__: new Inherited(), newMethod() {} } - person theflowersoftime; 01.06.2016
comment
@waldgeist, единственное преимущество использования одноэлементного класса заключается в том, что у вас есть свойства, с которыми вы хотите использовать геттеры и сеттеры вместо функций для получения и установки значений, которые можно перезаписать. - person mjwrazor; 24.02.2017
comment
И где вы ставите класс? Где конструктор? Почему бы не показать пример? - person shinzou; 25.04.2017
comment
Конечно, я тролль, и вы очень полезны и полезны, или, может быть, вы просто не знаете, и это лучший ответ, который вы могли придумать. И вы также ожидаете, что я буду хвалить плохие ответы, не все лицемеры. - person shinzou; 25.04.2017
comment
что, если вы хотите передать аргументы конструктору? - person Sagiv b.g; 15.02.2018
comment
Я сам использую конфигурацию в этом случае как синглтон и использую только один раз, когда конфигурация импортируется вместе с объявлением класса. - person King Friday; 15.02.2018
comment
Я согласен, что этого ответа недостаточно. Может ли автор уточнить, чем описанное поведение похоже на синглтон? - person Apollo; 13.11.2019
comment
@Apollo, так что я делаю это для простых вещей, к вашему сведению, не для всего. В узле или JS в целом, если вы экспортируете объект, он является синглтоном по определению глобального объекта. Вы можете получить к нему доступ, изменить его и т. Д., И он будет виден повсюду, используя его. - person King Friday; 13.11.2019


Чтобы создать шаблон Singleton, используйте один экземпляр с классами ES6;

'use strict';

import EventEmitter from 'events';

class Single extends EventEmitter {
    constructor() {
        this.state = {};
    }

    getState() {
        return this.state;
    }

}

export default let single = new Single();

Обновление: согласно объяснению @Bergi, ниже один не является допустимым аргументом.

Это работает благодаря (см. Стивен)

> If I understand CommonJS + the browser implementations correctly, the > output of a module is cached, so export default new MyClass() will > result in something that behaves as a singleton (only a single > instance of this class will ever exist per process/client depending on > env it's running in).

Вы можете найти пример здесь ES6 Singleton.

Примечание. Этот шаблон используется в Flux Dispacher.

Flux: www.npmjs.com/package/flux

Пример Dispacher: github.com/facebook/flux/blob/master/examples/flux-todomvc/js/dispatcher/AppDispatcher.js#L16

person Milan Karunarathne    schedule 12.11.2015
comment
Не делай этого! Просто не используйте синтаксис class, когда вам не нужен класс! Даже если этот шаблон используется во Flux, это не обязательно хороший шаблон. Кстати, эта цитата совершенно неверна, создать второй экземпляр с помощью new singleton.constructor тривиально. - person Bergi; 13.11.2015
comment
@Bergi позволяет предположить, что Dispatcher — это реализация класса, и я хочу расширить его функциональность. Итак, я создаю еще один класс с именем AppDispacher, используя JS ES6 extends, и мне нужен один экземпляр этого класса. Тогда мне нужно использовать этот подход? Но Джейсон сказал, что это бессмысленно. Итак, каков ваш подход? Я не понял одного. Вы делаете этот ответ неправильным. Но это работает и используется в такой компании, как Facebook. :) - person Milan Karunarathne; 13.11.2015
comment
Ну, это может иметь смысл, если вы используете несколько диспетчеров в своем приложении, но я бы не стал называть его синглтоном, даже если у вас есть модули, которые экспортируют только один экземпляр. - person Bergi; 13.11.2015
comment
@Bergi, как я понял, этот шаблон нельзя назвать синглтоном. Но это позволяет иметь один экземпляр, который может быть доступен в нескольких местах (в приложении), и нет никаких проблем с его использованием. - person Milan Karunarathne; 13.11.2015
comment
Хотя технически это не обязательно, мне нравится этот шаблон, поскольку он позволяет вам использовать «расширения». - person sam schonstal; 23.03.2016
comment
Что, если вашему классу нужно сначала получить параметры в своем ctor? - person shinzou; 25.04.2017

Синглтон класс

class SingletonClass {
    constructor( name = "", age = 0 ) {
        if ( !this.constructor.instance ) {

            this.constructor.instance = this;
            this.name = name;
            this.age = age;

        }

        return this.constructor.instance;
    }

    getName() {
        return this.name;
    }

    getAge() {
        return this.age;
    }
}
    
const instanceOne = new SingletonClass( "One", 25 );
const instanceTwo = new SingletonClass( "Two", 44 );

console.log( `Name of instanceOne is "${instanceOne.getName()}"` );
console.log( `Name of instanceTwo is "${instanceTwo.getName()}"` );

person M. Hamza Rajput    schedule 12.11.2019