Каков наилучший и удобный способ реализации шаблона Singleton для класса в TypeScript? (И с и без ленивой инициализации).
Как определить Singleton в TypeScript
Ответ 1
Синглтон-классы в TypeScript, как правило, являются антишаблоном. Вместо этого вы можете просто использовать пространства имен.
Бесполезный синглтон
class Singleton {
/* ... lots of singleton logic ... */
public someMethod() { ... }
}
// Using
var x = Singleton.getInstance();
x.someMethod();
Эквивалент пространства имен
export namespace Singleton {
export function someMethod() { ... }
}
// Usage
import { SingletonInstance} from "path/to/Singleton";
SingletonInstance.someMethod();
var x = SingletonInstance; // If you need to alias it for some reason
Ответ 2
Начиная с TS 2.0 у нас есть возможность определять модификаторы видимости для конструкторов, поэтому теперь мы можем создавать синглтоны в TypeScript так же, как мы привыкли к другим языкам.
Пример дан:
class MyClass
{
private static _instance: MyClass;
private constructor()
{
//...
}
public static get Instance()
{
// Do you need arguments? Make it a regular static method instead.
return this._instance || (this._instance = new this());
}
}
const myClassInstance = MyClass.Instance;
Спасибо @Drenai за указание на то, что если вы напишите код с использованием необработанного скомпилированного javascript, у вас не будет защиты от множественного создания экземпляров, поскольку ограничения TS исчезают и конструктор не будет скрыт.
Ответ 3
Лучший способ, который я нашел, это:
class SingletonClass {
private static _instance:SingletonClass = new SingletonClass();
private _score:number = 0;
constructor() {
if(SingletonClass._instance){
throw new Error("Error: Instantiation failed: Use SingletonClass.getInstance() instead of new.");
}
SingletonClass._instance = this;
}
public static getInstance():SingletonClass
{
return SingletonClass._instance;
}
public setScore(value:number):void
{
this._score = value;
}
public getScore():number
{
return this._score;
}
public addPoints(value:number):void
{
this._score += value;
}
public removePoints(value:number):void
{
this._score -= value;
}
}
Вот как вы его используете:
var scoreManager = SingletonClass.getInstance();
scoreManager.setScore(10);
scoreManager.addPoints(1);
scoreManager.removePoints(2);
console.log( scoreManager.getScore() );
http://www.codebelt.com/typescript/typescript-singleton-pattern/
Ответ 4
Следующий подход создает класс Singleton, который можно использовать как обычный класс:
class Singleton {
private static instance: Singleton;
//Assign "new Singleton()" here to avoid lazy initialisation
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this. member = 0;
Singleton.instance = this;
}
member: number;
}
Каждая операция new Singleton()
возвращает тот же экземпляр. Это может быть неожиданным для пользователя.
Следующий пример более прозрачен для пользователя, но требует другого использования:
class Singleton {
private static instance: Singleton;
//Assign "new Singleton()" here to avoid lazy initialisation
constructor() {
if (Singleton.instance) {
throw new Error("Error - use Singleton.getInstance()");
}
this.member = 0;
}
static getInstance(): Singleton {
Singleton.instance = Singleton.instance || new Singleton();
return Singleton.instance;
}
member: number;
}
Использование: var obj = Singleton.getInstance();
Ответ 5
Я удивлен, что не вижу здесь следующего шаблона, который выглядит очень просто.
// shout.ts
class ShoutSingleton {
helloWorld() { return 'hi'; }
}
export let Shout = new ShoutSingleton();
Использование
import { Shout } from './shout';
Shout.helloWorld();
Ответ 6
Вы можете использовать выражения класса для этого (начиная с 1.6).
var x = new (class {
/* ... lots of singleton logic ... */
public someMethod() { ... }
})();
или с именем, если ваш класс должен получить доступ к его типу внутри
var x = new (class Singleton {
/* ... lots of singleton logic ... */
public someMethod(): Singleton { ... }
})();
Другой вариант - использовать локальный класс внутри вашего синглтона, используя некоторые статические члены
class Singleton {
private static _instance;
public static get instance() {
class InternalSingleton {
someMethod() { }
//more singleton logic
}
if(!Singleton._instance) {
Singleton._instance = new InternalSingleton();
}
return <InternalSingleton>Singleton._instance;
}
}
var x = Singleton.instance;
x.someMethod();
Ответ 7
Добавьте следующие 6 строк в любой класс, чтобы сделать его "Singleton". Используйте ответ Alex, если вы предпочитаете получать экземпляр с помощью свойства, а не метода.
class MySingleton
{
private constructor(){ /* ... */}
private static _instance:MySingleton;
public static getInstance():MySingleton
{
return this._instance||(this._instance = new this());
};
}
var test = MySingleton.getInstance(); // will create the first instance
var test2 = MySingleton.getInstance(); // will return the first instance
alert(test === test2); // true
Ответ 8
Вы также можете использовать функцию Object.Freeze(). Это просто и легко:
class Singleton {
instance: any = null;
data: any = {} // store data in here
constructor() {
if (!this.instance) {
this.instance = this;
}
return this.instance
}
}
const singleton: Singleton = new Singleton();
Object.freeze(singleton);
export default singleton;
Ответ 9
Это, пожалуй, самый длинный процесс для создания singleton в typescript, но в больших приложениях это тот, который работал лучше для меня.
Сначала вам нужен класс Singleton, скажем, "./utils/Singleton.ts":
module utils {
export class Singleton {
private _initialized: boolean;
private _setSingleton(): void {
if (this._initialized) throw Error('Singleton is already initialized.');
this._initialized = true;
}
get setSingleton() { return this._setSingleton; }
}
}
Теперь представьте, что вам нужен одномодовый маршрутизатор. /navigation/Router.ts ":
/// <reference path="../utils/Singleton.ts" />
module navigation {
class RouterClass extends utils.Singleton {
// NOTICE RouterClass extends from utils.Singleton
// and that it isn't exportable.
private _init(): void {
// This method will be your "construtor" now,
// to avoid double initialization, don't forget
// the parent class setSingleton method!.
this.setSingleton();
// Initialization stuff.
}
// Expose _init method.
get init { return this.init; }
}
// THIS IS IT!! Export a new RouterClass, that no
// one can instantiate ever again!.
export var Router: RouterClass = new RouterClass();
}
Nice!, теперь инициализируйте или импортируйте, где вам нужно:
/// <reference path="./navigation/Router.ts" />
import router = navigation.Router;
router.init();
router.init(); // Throws error!.
Хорошая вещь о том, чтобы делать одиночные игры таким образом, состоит в том, что вы по-прежнему используете всю красоту классов typescript, это дает вам приятный intellisense, логика singleton сохраняет как-то раздельно, и ее легко удалить при необходимости.
Ответ 10
Мое решение для этого:
export default class Modal {
private static _instance : Modal = new Modal();
constructor () {
if (Modal._instance)
throw new Error("Use Modal.instance");
Modal._instance = this;
}
static get instance () {
return Modal._instance;
}
}
Ответ 11
В Typescript не обязательно следовать new instance()
методологии Singleton new instance()
. Импортированный статический класс без конструктора также может работать одинаково.
Рассматривать:
export class YourSingleton {
public static foo:bar;
public static initialise(_initVars:any):void {
YourSingleton.foo = _initvars.foo;
}
public static doThing():bar {
return YourSingleton.foo
}
}
Вы можете импортировать класс и ссылаться на YourSingleton.doThing()
в любом другом классе. Но помните, поскольку это статический класс, у него нет конструктора, поэтому я обычно использую метод intialise()
который вызывается из класса, который импортирует Singleton:
import {YourSingleton} from 'singleton.ts';
YourSingleton.initialise(params);
let _result:bar = YourSingleton.doThing();
Не забывайте, что в статическом классе каждый метод и переменная также должны быть статическими, поэтому вместо this
вы должны использовать полное имя класса YourSingleton
.
Ответ 12
я думаю, может быть, использовать дженерики
class Singleton<T>{
public static Instance<T>(c: {new(): T; }) : T{
if (this._instance == null){
this._instance = new c();
}
return this._instance;
}
private static _instance = null;
}
как пользоваться
шаг 1
class MapManager extends Singleton<MapManager>{
//do something
public init():void{ //do }
}
шаг 2
MapManager.Instance(MapManager).init();
Ответ 13
Вот еще один способ сделать это с помощью более обычного подхода javascript, используя IFFE:
module App.Counter {
export var Instance = (() => {
var i = 0;
return {
increment: (): void => {
i++;
},
getCount: (): number => {
return i;
}
}
})();
}
module App {
export function countStuff() {
App.Counter.Instance.increment();
App.Counter.Instance.increment();
alert(App.Counter.Instance.getCount());
}
}
App.countStuff();
Просмотрите демонстрацию
Ответ 14
Другой вариант - использовать символы в вашем модуле. Таким образом, вы можете защитить свой класс, также если конечный пользователь вашего API использует обычный Javascript:
let _instance = Symbol();
export default class Singleton {
constructor(singletonToken) {
if (singletonToken !== _instance) {
throw new Error("Cannot instantiate directly.");
}
//Init your class
}
static get instance() {
return this[_instance] || (this[_instance] = new Singleton(_singleton))
}
public myMethod():string {
return "foo";
}
}
Использование:
var str:string = Singleton.instance.myFoo();
Если пользователь использует ваш скомпилированный файл js API, также будет получена ошибка, если он попытается создать экземпляр вручную вашего класса:
// PLAIN JAVASCRIPT:
var instance = new Singleton(); //Error the argument singletonToken !== _instance symbol
Ответ 15
class MySingleton {
constructor(message) {
alert('hello' + message);
}
private static instance: MySingleton;
private static instance2: MySingleton;
public static getSessionStorageInstance() {
if (!MySingleton.instance) {
MySingleton.instance = new MySingleton("world");
}
return MySingleton.instance;
}
public static getSessionStorageInstance2() {
if (!MySingleton.instance2) {
MySingleton.instance2 = new MySingleton("youu");
}
return MySingleton.instance2;
}
}
const a: MySingleton = MySingleton.getSessionStorageInstance();
const b: MySingleton = MySingleton.getSessionStorageInstance2();
alert(a === b)
Ответ 16
Не чистый синглтон (инициализация может быть не ленивым), но похожий шаблон с помощью namespace
s.
namespace MyClass
{
class _MyClass
{
...
}
export const instance: _MyClass = new _MyClass();
}
Доступ к объекту Синглтона:
MyClass.instance
Ответ 17
Это самый простой способ
class YourSingletoneClass {
private static instance: YourSingletoneClass;
private constructor(public ifYouHaveAnyParams: string) {
}
static getInstance() {
if(!YourSingletoneClass.instance) {
YourSingletoneClass.instance = new YourSingletoneClass('If you have any params');
}
return YourSingletoneClass.instance;
}
}
Ответ 18
namespace MySingleton {
interface IMySingleton {
doSomething(): void;
}
class MySingleton implements IMySingleton {
private usePrivate() { }
doSomething() {
this.usePrivate();
}
}
export var Instance: IMySingleton = new MySingleton();
}
Таким образом, мы можем применить интерфейс, в отличие от принятого ответа Райана Кавано.