Ответы:
Одноэлементные классы в 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
export default new Singleton()
?
Начиная с 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 исчезают, и конструктор не будет скрыт.
Лучший способ, который я нашел:
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() );
https://codebelt.github.io/blog/typescript/typescript-singleton-pattern/
Следующий подход создает класс 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();
new Class(...)
синтаксис.
Я удивлен, что не вижу здесь следующего шаблона, который на самом деле выглядит очень простым.
// shout.ts
class ShoutSingleton {
helloWorld() { return 'hi'; }
}
export let Shout = new ShoutSingleton();
использование
import { Shout } from './shout';
Shout.helloWorld();
Shout
Вы можете использовать для этого выражения классов (я считаю, что с версии 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 строк к любому классу, чтобы сделать его «Синглтоном».
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
[Edit]: используйте ответ Alex, если вы предпочитаете получать экземпляр через свойство, а не через метод.
new MySingleton()
скажу 5 раз? ваш код резервирует один экземпляр?
я думаю, может быть, использовать дженерики быть жидким
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();
Вы также можете использовать функцию 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;
if (!this.instance)
внутри конструктора? Это дополнительная мера предосторожности на случай, если вы создали несколько экземпляров перед экспортом?
Я нашел новую версию этого, с которой компилятор Typescript полностью согласен, и я думаю, что она лучше, потому что она не требует getInstance()
постоянного вызова метода.
import express, { Application } from 'express';
export class Singleton {
// Define your props here
private _express: Application = express();
private static _instance: Singleton;
constructor() {
if (Singleton._instance) {
return Singleton._instance;
}
// You don't have an instance, so continue
// Remember, to set the _instance property
Singleton._instance = this;
}
}
Это имеет другой недостаток. Если у вас Singleton
есть какие-либо свойства, то компилятор Typescript подберет подходящий, если вы не инициализируете их значением. Вот почему я включил _express
свойство в свой примерный класс, потому что, если вы не инициализируете его значением, даже если вы назначите его позже в конструкторе, 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();
}
Отлично !, теперь инициализируйте или импортируйте туда, где вам нужно:
/// <reference path="./navigation/Router.ts" />
import router = navigation.Router;
router.init();
router.init(); // Throws error!.
При таком подходе синглтоны хорошо то, что вы по-прежнему используете всю красоту классов машинописного текста, это дает вам хороший 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;
}
}
return Modal._instance
. Таким образом, если вы используете new
этот класс, вы получите существующий объект, а не новый.
В Typescript не обязательно следовать new instance()
методологии Singleton. Импортированный статический класс без конструктора также может работать.
Рассматривать:
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
.
Вот еще один способ сделать это с помощью более традиционного подхода 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();
Посмотреть демо
Instance
переменной? Вы просто помещаете переменную и функции прямо под App.Counter
.
Другой вариант - использовать символы в вашем модуле. Таким образом вы можете защитить свой класс, даже если конечный пользователь вашего 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
Это самый простой способ
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;
}
}
namespace MySingleton {
interface IMySingleton {
doSomething(): void;
}
class MySingleton implements IMySingleton {
private usePrivate() { }
doSomething() {
this.usePrivate();
}
}
export var Instance: IMySingleton = new MySingleton();
}
Таким образом, мы можем применить интерфейс, в отличие от принятого ответа Райана Кавано.
После просмотра этого потока и экспериментов со всеми описанными выше параметрами я остановился на синглтоне, который можно создать с помощью подходящих конструкторов:
export default class Singleton {
private static _instance: Singleton
public static get instance(): Singleton {
return Singleton._instance
}
constructor(...args: string[]) {
// Initial setup
Singleton._instance = this
}
work() { /* example */ }
}
Это потребует первоначальной настройки (в main.ts
или index.ts
), которую можно легко реализовать с помощью
new Singleton(/* PARAMS */)
Затем в любом месте кода просто позвоните Singleton.instnace
; в этом случае, чтобы work
закончить, я бы позвонилSingleton.instance.work()