Как определить синглтон в TypeScript


каков наилучший и наиболее удобный способ реализации Одноэлементного шаблона для класса в TypeScript? (Как с ленивой инициализацией, так и без нее).

14 80

14 ответов:

Singleton-классы в TypeScript, как правило, является анти-паттерн. Вы можете просто использовать пространства имен.

бесполезный одноэлементный шаблон

class Singleton {
    /* ... lots of singleton logic ... */
    public someMethod() { ... }
}

// Using
var x = Singleton.getInstance();
x.someMethod();

эквивалент пространства имен

namespace Singleton {
    export function someMethod() { ... }
}
// Usage
Singleton.someMethod();
var x = Singleton; // If you need to alias it for some reason

начиная с TS 2.0, у нас есть возможность определить модификаторы видимости на конструкторы, Так что теперь мы можем делать синглеты в TypeScript так же, как мы привыкли с других языков.

пример:

class MyClass
{
    private static _instance: MyClass;

    private constructor()
    {
        //...
    }

    public static get Instance()
    {
        // Do you need arguments? Make it a regular method instead.
        return this._instance || (this._instance = new this());
    }
}

const myClassInstance = MyClass.Instance;

лучший способ, который я нашел:

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/

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

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

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

// shout.ts
class ShoutSingleton {
  helloWorld() { return 'hi'; }
}

export let Shout = new ShoutSingleton();

использование

import { Shout } from './shout';
Shout.helloWorld();

вы можете использовать выражения класса для этого (по состоянию на 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();

добавьте следующие 6 строк в любой класс, чтобы сделать его "Одноэлементным". Используйте Alex answer, если вы предпочитаете получать экземпляр через свойство, а не метод.

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

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

сначала вам нужен одноэлементный класс, скажем,"./ utils / Singleton.ТС":

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; }
    }
}

Теперь представьте, что вам нужен маршрутизатор singleton "./ навигация / маршрутизатор.ТС":

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

приятно!, теперь инициализируйте или импортируйте везде, где вам нужно:

/// <reference path="./navigation/Router.ts" />

import router = navigation.Router;

router.init();
router.init(); // Throws error!.

хорошая вещь о делаете синглтоны таким образом, вы все еще используете всю красоту классов typescript, это дает вам хороший intellisense, логика синглтона каким-то образом отделена, и ее легко удалить, если это необходимо.

мое решение для этого:

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;
    }
}

Я думаю, может быть, использовать дженерики быть тесто

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;
}

как использовать

step1

class MapManager extends Singleton<MapManager>{
     //do something
     public init():void{ //do }
}

step2

    MapManager.Instance(MapManager).init();

вот еще один способ сделать это с более традиционным подходом 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();

посмотреть демо

другой вариант-использовать символы в вашем модуле. Таким образом, вы можете защитить свой класс, также если конечный пользователь вашего 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();

если пользователь использует ваш скомпилированный файл API js, также будет получена ошибка, если он попытается вручную создать экземпляр вашего класса:

// PLAIN JAVASCRIPT: 
var instance = new Singleton(); //Error the argument singletonToken !== _instance symbol

в Typescript не обязательно следовать 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() метод, вызываемый из класса, который импортирует Синглтон:

import {YourSingleton} from 'singleton.ts';

YourSingleton.initialise(params);
let _result:bar = YourSingleton.doThing();

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

namespace MySingleton {
  interface IMySingleton {
      doSomething(): void;
  }
  class MySingleton implements IMySingleton {
      private usePrivate() { }
      doSomething() {
          this.usePrivate();
      }
  }
  export var Instance: IMySingleton = new MySingleton();
}

таким образом, мы можем применить интерфейс, в отличие от принятого ответа Райан Кавано.