TypeScriptでシングルトンを定義する方法


128

TypeScriptでクラスのシングルトンパターンを実装するための最良かつ最も便利な方法は何ですか?(遅延初期化の有無にかかわらず)。

回答:


87

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

55
シングルトンがアンチパターンと見なされるのはなぜでしょうか。このアプローチを検討するcodebelt.com/typescript/typescript-singleton-pattern
Victor

21
TypeScriptのシングルトンがアンチパターンと見なされる理由も知りたいです。また、コンストラクタパラメータがない場合は、なぜexport default new Singleton()ですか?
emzero

23
名前空間ソリューションは、シングルトンではなく、静的クラスのように見えます
MihaiRăducanu2016年

6
同じように動作します。C#では、静的クラスを値のように(つまり、シングルトンクラスのインスタンスのように)渡すことができないため、その有用性が制限されます。TypeScriptでは、インスタンスのように名前空間渡すことができます。そのため、シングルトンクラスは必要ありません。
Ryan Cavanaugh、2016年

13
名前空間をシングルトンとして使用する場合の制限は、(私の知る限り)インターフェースを実装できないことです。この@ryanに同意しますか
Gabe O'Leary

182

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;

TSの制約がなくなり、コンストラクターが隠されないため、生のコンパイルされたJavaScriptを使用してコードを記述した場合、複数のインスタンス化に対する保護がなくなることを指摘してくれて@Drenaiに感謝します。


2
コンストラクタはプライベートにすることができますか?
エキスパートは2016

2
:@Expertwannabeこれは、TS 2.0で利用可能になりましたgithub.com/Microsoft/TypeScript/wiki/...
アレックス・

3
これが私の好ましい答えです!ありがとうございました。
Martin Majewski、2016

1
fyi、複数インスタンスの理由は、ノードモジュールの解決が邪魔になるためです。したがって、ノードにシングルトンを作成する場合は、それが考慮されていることを確認してください。最終的に、srcディレクトリの下にnode_modulesフォルダーを作成し、そこにシングルトンを配置しました。
webteckie

3
@KimchiManプロジェクトがtypescript以外の環境で使用された場合(JSプロジェクトにインポートされた場合など)、クラスにはそれ以上のインスタンス化に対する保護がありません。それは純粋なTS環境でのみ機能しますが、JSライブラリ開発では機能しません
Drenai

39

私が見つけた最良の方法は:

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/


3
コンストラクタをプライベートにしませんか?
Phil Mander 2016

4
私は、投稿がTSにプライベートコンストラクターを持つ能力よりも前にあると思います。github.com/Microsoft/TypeScript/issues/2341
Trevor

私はこの答えが好きです。開発中のプライベートコンストラクターは優れていますが、トランスパイルされたTSモジュールがJS環境にインポートされた場合でも、コンストラクターにアクセスできます。このアプローチでは、誤用からほぼ保護されています... SingletonClass ['_ instance']がnull / undefinedに設定されていない限り
Drenai

リンクが壊れています。これは実際のリンクだと思います:codebelt.github.io/blog/typescript/typescript-singleton-pattern
El Asiduo

24

次のアプローチでは、従来のクラスとまったく同じように使用できるシングルトンクラスを作成します。

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


1
これが実装方法です。ギャングオブフォーに同意できないことが1つあるとしたら、それはおそらく1 つだけです -そのシングルトンパターン。おそらく、C / ++はこのように設計することを妨げます。しかし、私に尋ねると、クライアントコードはシングルトンかどうかを気にすべきではありません。クライアントはnew Class(...)構文を実装する必要があります。
コーディ

16

非常にシンプルに見える次のパターンがここに表示されないことに驚いています。

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

export let Shout = new ShoutSingleton();

使用法

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

次のエラーメッセージが表示されました。エクスポートされた変数 'Shout'はプライベート名 'ShoutSingleton'を持っているか、使用しています。
Twois

3
クラス 'ShoutSingleton'もエクスポートする必要があり、エラーが消えます。
Twois

そうです、私も驚いています。なぜクラスに悩むのですか?シングルトンは内部の仕組みを隠すことになっています。関数helloWorldをエクスポートしないのはなぜですか?
Oleg Dulin 2017

詳細については、このgithubの問題を参照してください:github.com/Microsoft/TypeScript/issues/6307
Ore4444

5
Shoutただし、ユーザーが新しいクラスを作成することを
妨げる

7

これにはクラス式を使用できます(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行を任意のクラスに追加して、「シングルトン」にします。

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の回答を使用します。


私がやるとどうなるか new MySingleton()、5回を言いますか?あなたのコードは単一のインスタンスを予約しますか?
Hlawuleka MAS

「new」は絶対に使用しないでください。Alexが書いたように、コンストラクターは「プライベート」で、「new MySingleton()」を実行できないようにする必要があります。正しい使い方は、MySingleton.getInstance()を使用してインスタンスを取得することです。AKAIK(私の例のように)何のコンストラクタは、公共空のコンストラクタ=ん
Flavien Volken

「あなたは決して「新しい」を使用すべきではない-まさに私のポイント:」しかし、あなたの実装は私がそうすることをどのように妨げていますか?クラスにプライベートコンストラクターがある場所はどこにもありませんか?
Hlawuleka MAS 2018

@HlawulekaMAS私はしませんでした…したがって、私は答えを編集しました。TS2.0以前はプライベートコンストラクターが不可能であったことに注意してください(つまり、最初に答えを書いたとき)
Flavien Volken

「つまり、私が最初に答えを書いたとき」-理にかなっています。涼しい。
Hlawuleka MAS 2018

3

多分ジェネリックを使うとね

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

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;

ケニー、freeze()の良い点ですが、2つの注意事項があります:(1)フリーズ(シングルトン)した後でも、singleton.dataを変更できます。他の属性(data2など)を追加することはできませんが、フリーズ( )は完全凍結ではありません:)および(2)クラスシングルトンは複数のインスタンスの作成を許可します(例obj1 = new Singleton(); obj2 = new Singleton();)したがって、シングルトンはシングルトンではありません
:)

シングルトンクラスを他のファイルにインポートすると、常に同じインスタンスが取得され、「data」のデータは他のすべてのインポート間で一貫します。それは私にとってシングルトンです。エクスポートされたシングルトンインスタンスが1度だけ作成されることを確認する際のフリーズ。
ケニー

ケニー、(1)他のファイルにクラスをインポートした場合、インスタンスは取得されません。インポートすることで、クラス定義をスコープに含めるだけで、新しいインスタンスを作成できます。次に、特定のクラスの1つ以上のインスタンスを1つまたは複数のファイルで作成できます。これは、シングルトンのアイデアの目的全体に反します。(2)ドキュメントから:Object.freeze()メソッドはオブジェクトをフリーズします。フリーズしたオブジェクトは変更できなくなりました。オブジェクトを凍結すると、新しいプロパティが追加されなくなります。(引用の終わり)つまり、freeze()を使用しても、複数のオブジェクトを作成できます。
Dmitry Shevkoplyas

正しいが、この場合はそうです。エクスポートされたメンバーはすでにインスタンスであるためです。そして、インスタンスはデータを保持します。クラスにエクスポートを配置する場合も同様で、複数のインスタンスを作成できます。
ケニー

@kennyあなたがインスタンスをエクスポートするつもりであることがわかっているのなら、なぜコンストラクタでに悩むのif (!this.instance)ですか これは、エクスポートの前に複数のインスタンスを作成した場合の特別な予防策ですか?
Alex

2

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はプロパティが定義されていないと考えるため、サンプルクラスにプロパティを含めたのはそのためです。これはストリクトモードを無効にすることで修正できますが、できればできません。また、このメソッドには別の欠点もあります。コンストラクタが実際に呼び出されるため、コンストラクタが実行されるたびに、別のインスタンスが技術的に作成されますが、アクセスできません。これにより、理論上、メモリリークが発生する可能性があります。


1

これはおそらくtypescriptでシングルトンを作成するのに最も長いプロセスですが、より大きなアプリケーションでは、私にとってよりうまくいったものです。

まず、"./ 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!.

このようにシングルトンを実行することの良い点は、typescriptクラスのすべての美しさを引き続き使用できることです。これにより、優れたインテリセンスが得られます。シングルトンロジックは何らかの方法で分離され、必要に応じて簡単に削除できます。


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

1
コンストラクタでは、例外の代わりにできreturn Modal._instanceます。この方法では、newそのクラスの場合、新しいオブジェクトではなく、既存のオブジェクトを取得します。
MihaiRăducanu2016年

1

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


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

デモを見る


Instance変数を追加する理由は何ですか?変数と関数をの直下に置くだけApp.Counterです。
fyaa

@fyaaはい、可能ですが、変数と関数はApp.Counterの直下にありますが、このアプローチはシングルトンパターンen.wikipedia.org/wiki/Singleton_patternによく適合すると思います。
JesperA 2016

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

0

純粋なシングルトン(初期化は遅延ではない可能性があります)ではなく、namespacesを使用した同様のパターン。

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

シングルトンのオブジェクトへのアクセス:

MyClass.instance

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

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

この方法では、Ryan Cavanaughの受け入れられた回答とは異なり、インターフェイスを適用できます。


-1

このスレッドを精査し、上記のすべてのオプションを試した後、適切なコンストラクターで作成できるシングルトンで解決しました。

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


なぜ誰かが実際に改善についてコメントせずに回答を反対票を投じるのですか 私たちはコミュニティです
TheGeekZn
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.