如何在 TypeScript 中定义单例

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

172064 次浏览

下面的方法创建一个 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();

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

我发现最好的办法是:

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 类,比方说,“ ./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; }
}
}

现在假设您需要一个路由器单例 “ ./导航/路由器. 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!.

这样做的好处是你仍然可以使用类型脚本的所有优点,它给你很好的智能感觉,单例逻辑保持某种程度的分离,如果需要的话很容易删除。

您可以为此使用类表达式(我相信从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();

这里还有另一种使用 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();

查看 小样

将以下6行添加到任何类中,使其成为“ Singleton”。

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

[编辑] : 如果您更喜欢通过属性而不是方法获取实例,那么使用 Alex answer。

自从 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 的约束消失了,构造函数也不会被隐藏。

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

我的解决办法是:

export default class Singleton {
private static _instance: Singleton = new Singleton();


constructor() {
if (Singleton._instance)
throw new Error("Use Singleton.instance");
Singleton._instance = this;
}


static get instance() {
return Singleton._instance;
}
}


2021年最新情况

现在构造函数可以是私有的

export default class Singleton {
private static _instance?: Singleton;


private constructor() {
if (Singleton._instance)
throw new Error("Use Singleton.instance instead of new.");
Singleton._instance = this;
}


static get instance() {
return Singleton._instance ?? (Singleton._instance = new Singleton());
}
}

在 TS 游乐场进行测试

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

这样我们就可以应用一个接口,不像 Ryan Cavanaugh 所接受的答案。

我很惊讶在这里没有看到下面的模式,它实际上看起来非常简单。

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


export let Shout = new ShoutSingleton();

用法

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

在 Typecript 中,不一定要遵循 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();

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

我觉得用仿制药比较好

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

怎么用

第一步

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

第二步

    MapManager.Instance(MapManager).init();

你也可以使用函数 反对,不许动,它简单易用:

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;

不是纯粹的单例(初始化可能不是惰性的) ,而是借助于 namespace的类似模式。

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

获取 Singleton 的目标:

MyClass.instance

这是最简单的方法

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

我发现了一个新的版本,类型脚本编译器完全可以接受,我认为这个版本更好,因为它不需要不断调用 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确实有任何属性,那么类型脚本编译器将抛出一个适合,除非您使用值初始化它们。这就是为什么我在我的示例类中包含了一个 _express属性,因为除非你用一个值初始化它,即使你稍后在构造函数中赋值它,Typecript 会认为它还没有被定义。这可以通过禁用严格模式来修复,但如果可能的话,我宁愿不这样做。这个方法还有另外一个缺点,我必须指出,因为构造函数实际上是被调用的,每次它做另一个实例时,从技术上来说是创建的,但是不可访问。理论上,这可能会导致内存泄漏。

在仔细研究了这个线程和上面的所有选项之后,我决定使用一个可以用合适的构造函数创建的 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()

在实现了一个经典模式之后,比如

class Singleton {
private instance: Singleton;
  

private constructor() {}


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

我意识到这是非常无用的,如果你想其他类也是单例的。这是不可扩展的。你必须为每个你想成为一个单例的类编写单例的东西。

救援装饰师。

@singleton
class MyClassThatIsSingletonToo {}

你可以自己写装饰,或者从 npm 中获取一些。我发现 这个代理的实现从 @ keenonDrum/singleton包足够整洁。

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

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

一些文件

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


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

我一直在努力寻找一个合适的解决方案,用打字机声明单例模式。

我认为以下是更实际的解决方案。

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);
/**
* The Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
class Singleton {
private static instance: Singleton;


/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private constructor() { }


/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}


return Singleton.instance;
}


/**
* Finally, any singleton should define some business logic, which can be
* executed on its instance.
*/
public someBusinessLogic() {
// ...
}
}


/**
* The client code.
*/
function clientCode() {
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();


if (s1 === s2) {
console.log('Singleton works, both variables contain the same instance.');
} else {
console.log('Singleton failed, variables contain different instances.');
}
}


clientCode();
class Singleton<CREATOR extends () => any>
{
private m_fnGetInstance: () => ReturnType<CREATOR>;
public get Instance() { return this.m_fnGetInstance(); }
public constructor(creator: CREATOR) {
this.m_fnGetInstance = () => {
const instance = creator();
this.m_fnGetInstance = () => instance;
return instance;
};
}
}


const Foo = new Singleton(() => new class {
public bar() {}
});


Foo.Instance.bar();

它之所以快是因为没有条件分支。

游乐场

Github