如何在TypeScript中定义Singleton

时间:2015-05-11 17:35:09

标签: singleton typescript

在TypeScript中为类实现Singleton模式的最佳和最方便的方法是什么? (有和没有懒惰初始化)。

24 个答案:

答案 0 :(得分:140)

从TS 2.0开始,我们可以定义visibility modifiers on constructors,所以现在我们可以在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的约束消失,构造函数不会被隐藏。

答案 1 :(得分:77)

TypeScript中的Singleton类通常是反模式。您可以简单地使用命名空间。

无用的单例模式

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 :(得分:37)

我找到的最好方法是:

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/

答案 3 :(得分:18)

以下方法创建了一个可以像传统类一样使用的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();

答案 4 :(得分:14)

我很惊讶在这里看不到以下模式,实际上看起来非常简单。

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

export let Shout = new ShoutSingleton();

用法

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

答案 5 :(得分: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();

答案 6 :(得分:4)

将以下6行添加到任何类中以使其成为&#34; Singleton&#34;。 如果您希望通过属性而不是方法获取实例,请使用Alex答案。

class MySingleton
{
    private constructor(){ /* ... */}
    private static _instance:MySingleton;
    public static getInstance():MySingleton
    {
        return this._instance||(this._instance = new this());
    };
}

Test example:

var test = MySingleton.getInstance(); // will create the first instance
var test2 = MySingleton.getInstance(); // will return the first instance
alert(test === test2); // true

答案 7 :(得分:3)

您还可以使用函数 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;

答案 8 :(得分:1)

在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()。但请记住,因为这是一个静态类,它没有构造函数所以我通常使用从导入Singleton的类调用的intialise()方法:

import {YourSingleton} from 'singleton.ts';

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

不要忘记,在静态类中,每个方法和变量也需要是静态的,因此您将使用完整的类名this而不是YourSingleton

答案 9 :(得分:1)

我发现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也会认为它没有被定义。可以通过禁用严格模式来解决此问题,但我不希望这样做。我应该指出,此方法还有另一个缺点,因为构造器实际上每次被调用时,都会在技术上创建另一个实例,但无法访问它。从理论上讲,这可能会导致内存泄漏。

答案 10 :(得分:1)

我认为也许使用泛型会更糟

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

答案 11 :(得分:1)

我的解决方案:

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

答案 12 :(得分:1)

这可能是在打字稿中制作单例的最长过程,但在较大的应用程序中,这对我来说效果更好。

首先你需要一个Singleton课程,让我们说,&#34; ./ utils / Singleton.ts&#34;

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

现在想象你需要一个路由器单件&#34; ./ navigation / Router.ts&#34;

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

import router = navigation.Router;

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

很好!,现在可以根据需要进行初始化或导入:

{{1}}

以这种方式做单身的好处在于你仍然使用所有打字稿类的优点,它给你很好的智能感知,单身逻辑保持分离,如果需要它很容易删除。

答案 13 :(得分:0)

另一种选择是在模块中使用符号。这样,如果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

答案 14 :(得分:0)

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)

答案 15 :(得分:0)

这是另一种使用IFFE更常规的javascript方法来实现它的方法:

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

查看demo

答案 16 :(得分:0)

不是单纯的单例(初始化可能不会很懒),而是借助namespace s来实现类似的模式。

namespace MyClass
{
    class _MyClass
    {
    ...
    }
    export const instance: _MyClass = new _MyClass();
}

访问Singleton对象:

MyClass.instance

答案 17 :(得分:0)

这是最简单的方法

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 :(得分:0)

在检查了该线程并使用了上面的所有选项之后,我选择了可​​以通过适当的构造函数创建的Singleton:

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.tsindex.ts中),可以通过
轻松实现 new Singleton(/* PARAMS */)

然后,在代码中的任何地方,只需调用Singleton.instnace;在这种情况下,要完成work,我会打电话给Singleton.instance.work()

答案 19 :(得分:0)

在实现像这样的经典模式之后

class Singleton {
  private instance: Singleton;
  
  private constructor() {}

  public getInstance() {
    if (!this.instance) { 
      this.instance = new Singleton();
    }
    return this.instance;
  }
}

我意识到,如果您希望其他类也成为单身人士,这将毫无用处。它不可扩展。你必须为每个你想成为单身人士的班级编写单身人士的东西。

救援装饰。

@singleton
class MyClassThatIsSingletonToo {}

您可以自己编写装饰器,也可以从 npm 中获取一些。我发现 this 包中的 @keenondrums/singleton 基于代理的实现足够简洁。

答案 20 :(得分:0)

举个例子,我想创建单例类,通过它我将能够创建客户端的连接,然后我想在任何地方使用相同的连接客户端。

import nats, { Stan } from 'node-nats-streaming';

class NatsWrapper {
  private _client?: Stan;

  get client() {
    if (!this._client) {
      throw new Error('Cannot access NATS client before connecting');
    }
    return this._client;
  }

  connect(clusterId: string, clientId: string, url: string) {
    this._client = nats.connect(clusterId, clientId, { url });

    return new Promise((resolve, reject) => {
      this.client.on('connect', (result) => {
        console.log('Connected to NATS');
        resolve(result);
      });
      this.client.on('error', (err) => {
        reject(err);
      });
    });
  }
}

// since we create and export the instace, it will act like a singleton
export const natsWrapper = new NatsWrapper();

现在,首先在您的 index.ts/app.ts 文件中创建连接,然后您只需在任何地方导入即可访问同一个客户端

index.ts

    await natsWrapper.connect(
      'ticketing',
      'client_id_random_str',
      'http://nats-srv:4222'
    );

someFile.ts

import { natsWrapper } from '../nats-wrapper';

const abc = () =>{
    console.log(natsWrapper.client)
}

答案 21 :(得分:0)

我一直在努力寻找在打字稿中声明单例模式类的合适解决方案。

我认为下面是更实用的解决方案。

class MySingletonClass {
    public now:Date = new Date();
    public arg:string;
    constructor(arg:string) {
        this.arg = arg;

        // Make singleton
        if ('instance' in MySingletonClass) return Object.getOwnPropertyDescriptor(MySingletonClass, 'instance')?.value;
        Object.assign(MySingletonClass, { instance: this });
    }
}

const a = new MySingletonClass('a');
console.log(a);

const b = new MySingletonClass('b');
console.log(b);

console.log('a === b', a === b);
console.log('a.now === b.now', a.now === b.now);

答案 22 :(得分:-1)

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

这样我们可以应用界面,这与Ryan Cavanaugh接受的答案不同。

答案 23 :(得分:-1)

这是一个打字稿装饰器,用于检查是否偶然针对设计为单例的服务类创建了多个实例:

https://www.npmjs.com/package/singleton-checker