So definieren Sie Singleton in TypeScript

128

Was ist der beste und bequemste Weg, um ein Singleton-Muster für eine Klasse in TypeScript zu implementieren? (Sowohl mit als auch ohne verzögerte Initialisierung).

Maja
quelle

Antworten:

87

Singleton-Klassen in TypeScript sind im Allgemeinen ein Anti-Pattern. Sie können stattdessen einfach Namespaces verwenden.

Nutzloses Singleton-Muster

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

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

Namespace-Äquivalent

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
Ryan Cavanaugh
quelle
55
es wäre schön bis jetzt, warum der Singleton als Anti-Pattern angesehen wird? Betrachten Sie diesen Ansatz codebelt.com/typescript/typescript-singleton-pattern
Victor
21
Ich würde gerne wissen, warum Singletons in TypeScript auch als Anti-Pattern gelten. Und auch wenn es keine Konstruktorparameter gibt, warum nicht export default new Singleton()?
Emzero
23
Die Namespace-Lösung sieht eher aus wie eine statische Klasse, nicht wie ein Singleton
Mihai Răducanu
6
Es verhält sich genauso. In C # können Sie eine statische Klasse nicht so weitergeben, als wäre sie ein Wert (dh als wäre sie eine Instanz einer Singleton-Klasse), was ihre Nützlichkeit einschränkt. In Typoskript, Sie können einen Namespace um wie eine Instanz übergeben. Deshalb brauchen Sie keine Singleton-Klassen.
Ryan Cavanaugh
13
Eine Einschränkung bei der Verwendung eines Namespace als Singleton besteht darin, dass er (meines Wissens) keine Schnittstelle implementieren kann. Würden Sie diesem @ryan
Gabe O'Leary
182

Seit TS 2.0 können wir Sichtbarkeitsmodifikatoren für Konstruktoren definieren , sodass wir jetzt Singletons in TypeScript erstellen können, wie wir es von anderen Sprachen gewohnt sind.

Beispiel gegeben:

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;

Vielen Dank an @Drenai für den Hinweis, dass Sie beim Schreiben von Code mit dem rohen kompilierten Javascript keinen Schutz vor mehrfacher Instanziierung haben, da die Einschränkungen von TS verschwinden und der Konstruktor nicht ausgeblendet wird.

Alex
quelle
2
Der Konstruktor könnte privat sein?
Experte will
2
@Expertwannabe Dies ist jetzt in TS 2.0 verfügbar: github.com/Microsoft/TypeScript/wiki/…
Alex
3
Dies ist meine bevorzugte Antwort! Danke dir.
Martin Majewski
1
Zu Ihrer Information, der Grund für die mehreren Instanzen war die Auflösung des Knotenmoduls, die im Weg stand. Wenn Sie also einen Singleton im Knoten erstellen, stellen Sie sicher, dass dies berücksichtigt wird. Am Ende habe ich einen Ordner node_modules unter meinem Verzeichnis src erstellt und den Singleton dort abgelegt.
Webteckie
3
@KimchiMan Wenn das Projekt jemals in einer Umgebung ohne Typoskript verwendet wird, z. B. in ein JS-Projekt importiert, hat die Klasse keinen Schutz vor weiterer Instanziierung. Es funktioniert nur in einer reinen TS-Umgebung, aber nicht für die Entwicklung von JS-Bibliotheken
Drenai
39

Der beste Weg, den ich gefunden habe, ist:

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

}

So verwenden Sie es:

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/

codeBelt
quelle
3
Warum nicht den Konstruktor privat machen?
Phil Mander
4
Ich denke, der Beitrag geht der Fähigkeit voraus, private Konstruktoren in TS zu haben. github.com/Microsoft/TypeScript/issues/2341
Trevor
Ich mag diese Antwort. Private Konstruktoren eignen sich hervorragend für die Entwicklung. Wenn jedoch ein transpiliertes TS-Modul in eine JS-Umgebung importiert wird, kann weiterhin auf den Konstruktor zugegriffen werden. Mit diesem Ansatz ist es fast vor Missbrauch geschützt ... es sei denn, die SingletonClass ['_ Instanz'] ist auf null / undefiniert gesetzt
Drenai
Die Verbindung ist unterbrochen. Ich denke, dies ist der eigentliche Link: codebelt.github.io/blog/typescript/typescript-singleton-pattern
El Asiduo
24

Der folgende Ansatz erstellt eine Singleton-Klasse, die genau wie eine herkömmliche Klasse verwendet werden kann:

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

Jede new Singleton()Operation gibt dieselbe Instanz zurück. Dies kann jedoch vom Benutzer unerwartet sein.

Das folgende Beispiel ist für den Benutzer transparenter, erfordert jedoch eine andere Verwendung:

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

Verwendung: var obj = Singleton.getInstance();

Maja
quelle
1
So sollte es umgesetzt werden. Wenn es eine Sache gibt, mit der ich The Gang of Four nicht einverstanden bin - und es ist wahrscheinlich nur eine -, dann ist es das Singleton-Muster. Vielleicht hindert C / ++ einen daran, es so zu gestalten. Aber wenn Sie mich fragen, sollte der Client-Code nicht wissen oder sich darum kümmern, ob es sich um einen Singleton handelt. Clients sollten weiterhin die new Class(...)Syntax implementieren .
Cody
16

Ich bin überrascht, das folgende Muster hier nicht zu sehen, das eigentlich sehr einfach aussieht.

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

export let Shout = new ShoutSingleton();

Verwendung

import { Shout } from './shout';
Shout.helloWorld();
Romain Bruckert
quelle
Ich habe die folgende Fehlermeldung erhalten: Die exportierte Variable 'Shout' hat oder verwendet den privaten Namen 'ShoutSingleton'.
Twois
3
Sie müssen auch die Klasse 'ShoutSingleton' exportieren, und der Fehler verschwindet.
Twois
Richtig, ich bin auch überrascht. Warum sollte man sich überhaupt mit der Klasse beschäftigen? Singletons sollen ihre internen Abläufe verbergen. Warum nicht einfach die Funktion helloWorld exportieren?
Oleg Dulin
Weitere Informationen finden Sie in diesem Github-Problem: github.com/Microsoft/TypeScript/issues/6307
Ore4444
5
Shout
Vermutlich hindert
7

Sie können hierfür Klassenausdrücke verwenden (ab 1.6 glaube ich).

var x = new (class {
    /* ... lots of singleton logic ... */
    public someMethod() { ... }
})();

oder mit dem Namen, wenn Ihre Klasse intern auf ihren Typ zugreifen muss

var x = new (class Singleton {
    /* ... lots of singleton logic ... */
    public someMethod(): Singleton { ... }
})();

Eine andere Möglichkeit besteht darin, eine lokale Klasse in Ihrem Singleton mit einigen statischen Elementen zu verwenden

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

Fügen Sie jeder Klasse die folgenden 6 Zeilen hinzu, um sie zu "Singleton" zu machen.

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

Testbeispiel:

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

[Bearbeiten]: Verwenden Sie die Alex-Antwort, wenn Sie die Instanz lieber über eine Eigenschaft als über eine Methode abrufen möchten.

Flavien Volken
quelle
Was passiert, wenn ich new MySingleton()5 mal sage? Reserviert Ihr Code eine einzelne Instanz?
Hlawuleka MAS
Sie sollten niemals "new" verwenden: Wie Alex schrieb, sollte der Konstruktor "privat" sein und "new MySingleton ()" verhindern. Die richtige Verwendung besteht darin, eine Instanz mit MySingleton.getInstance () abzurufen. AKAIK kein Konstruktor (wie in meinem Beispiel) = ein öffentlicher leerer Konstruktor
Flavien Volken
"Sie sollten niemals" neu "verwenden - genau mein Punkt:". Aber wie hindert mich Ihre Implementierung daran? Ich sehe nirgendwo, wo Sie einen privaten Konstruktor in Ihrer Klasse haben?
Hlawuleka MAS
@HlawulekaMAS Ich habe nicht ... Ich habe daher die Antwort bearbeitet, beachten Sie, dass ein privater Konstruktor vor TS 2.0 nicht möglich war (dh zu dem Zeitpunkt, als ich die Antwort zuerst schrieb)
Flavien Volken
"dh zu der Zeit, als ich die Antwort zuerst schrieb" - Sinnvoll. Cool.
Hlawuleka MAS
3

Ich denke, vielleicht verwenden Generika Teig

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

wie benutzt man

Schritt 1

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

Schritt 2

    MapManager.Instance(MapManager).init();
Sanye
quelle
3

Sie können auch die Funktion Object.Freeze () verwenden . Es ist einfach und leicht:

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;
kenny
quelle
Kenny, guter Punkt beim Einfrieren (), aber zwei Anmerkungen: (1) Nach dem Einfrieren (Singleton) können Sie singleton.data noch ändern. Sie können kein anderes Attribut (wie data2) hinzufügen, aber der Punkt ist das Einfrieren ( ) ist nicht tiefgefroren :) und (2) Ihre Klasse Singleton erlaubt es, mehr als eine Instanz zu erstellen (Beispiel obj1 = new Singleton (); obj2 = new Singleton ();), also ist Ihr Singleton nicht Singleton
:)
Wenn Sie die Singleton-Klasse in andere Dateien importieren, erhalten Sie immer dieselbe Instanz und die Daten in 'data' sind zwischen allen anderen Importen konsistent. Das ist für mich ein Singleton. Das Einfrieren, um sicherzustellen, dass die exportierte Singleton-Instanz nur einmal erstellt wird.
Kenny
Kenny, (1) Wenn Sie Ihre Klasse in andere Dateien importieren, erhalten Sie keine Instanz. Durch den Import bringen Sie einfach die Klassendefinition in den Geltungsbereich, damit Sie neue Instanzen erstellen können. Dann können Sie> 1 Instanzen der angegebenen Klasse erstellen, sei es in einer Datei oder in mehreren Dateien, was dem gesamten Zweck der Singleton-Idee widerspricht. (2) Aus Dokumenten: Die Object.freeze () -Methode friert ein Objekt ein. Ein eingefrorenes Objekt kann nicht mehr geändert werden. Durch das Einfrieren eines Objekts wird verhindert, dass neue Eigenschaften hinzugefügt werden. (Ende des Zitats) Das bedeutet, dass freeze () Sie nicht daran hindert, mehrere Objekte zu erstellen.
Dmitry Shevkoplyas
Richtig, aber in diesem Fall ist dies der Fall, da das exportierte Mitglied bereits eine Instanz ist. Und die Instanz behält die Daten. Wenn Sie die Klasse ebenfalls exportieren, haben Sie Recht und können mehrere Instanzen erstellen.
Kenny
@kenny Wenn Sie wissen, dass Sie eine Instanz exportieren werden, warum sollten Sie sich dann mit dem if (!this.instance)im Konstruktor beschäftigen? Ist dies nur eine zusätzliche Vorsichtsmaßnahme, falls Sie vor dem Export mehrere Instanzen erstellt haben?
Alex
2

Ich habe eine neue Version davon gefunden, mit der der Typescript-Compiler völlig einverstanden ist, und ich denke, sie ist besser, weil nicht getInstance()ständig eine Methode aufgerufen werden muss.

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

Dies hat einen anderen Nachteil. Wenn Ihre SingletonEigenschaften vorhanden sind, gibt der Typescript-Compiler eine Anpassung aus, es sei denn, Sie initialisieren sie mit einem Wert. Aus diesem Grund habe ich eine _expressEigenschaft in meine Beispielklasse aufgenommen, da Typescript denkt, dass sie nicht definiert wurde, es sei denn, Sie initialisieren sie mit einem Wert, auch wenn Sie sie später im Konstruktor zuweisen. Dies könnte durch Deaktivieren des strengen Modus behoben werden, aber ich ziehe es vor, wenn möglich, dies nicht zu tun. Diese Methode hat noch einen weiteren Nachteil, auf den ich hinweisen sollte, da der Konstruktor tatsächlich aufgerufen wird und jedes Mal, wenn er dies tut, eine andere Instanz technisch erstellt wird, auf die jedoch nicht zugegriffen werden kann. Dies könnte theoretisch zu Speicherlecks führen.

Eagerestwolf
quelle
1

Dies ist wahrscheinlich der längste Prozess, um einen Singleton in Typoskript zu erstellen, aber in größeren Anwendungen hat er für mich besser funktioniert.

Zuerst benötigen Sie eine Singleton-Klasse in "./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; }
    }
}

Stellen Sie sich nun vor, Sie benötigen einen Router-Singleton "./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();
}

Nice!, Jetzt initialisieren oder importieren, wo immer Sie brauchen:

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

import router = navigation.Router;

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

Das Schöne an Singletons auf diese Weise ist, dass Sie immer noch die Schönheit von Typoskriptklassen nutzen, dass Sie eine gute Intelligenz haben, die Singleton-Logik irgendwie getrennt bleibt und bei Bedarf leicht entfernt werden kann.

a.guerrero.g87
quelle
1

Meine Lösung dafür:

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;
    }
}
Daniel de Andrade Varela
quelle
1
Im Konstruktor können Sie anstelle der Ausnahme return Modal._instance. Auf diese Weise erhalten Sie, wenn Sie newdiese Klasse haben, das vorhandene Objekt, kein neues.
Mihai Răducanu
1

In Typescript muss man nicht unbedingt der new instance()Singleton-Methode folgen . Eine importierte statische Klasse ohne Konstruktor kann genauso gut funktionieren.

Erwägen:

export class YourSingleton {

   public static foo:bar;

   public static initialise(_initVars:any):void {
     YourSingleton.foo = _initvars.foo;
   }

   public static doThing():bar {
     return YourSingleton.foo
   }
}

Sie können die Klasse importieren und YourSingleton.doThing()in jeder anderen Klasse darauf verweisen . Aber denken Sie daran, da es sich um eine statische Klasse handelt, die keinen Konstruktor hat. Daher verwende ich normalerweise eine intialise()Methode, die von einer Klasse aufgerufen wird, die den Singleton importiert:

import {YourSingleton} from 'singleton.ts';

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

Vergessen Sie nicht, dass in einer statischen Klasse jede Methode und Variable auch statisch sein muss, damit Sie stattdessen thisden vollständigen Klassennamen verwenden YourSingleton.

Dominic Lee
quelle
0

Hier ist noch eine andere Möglichkeit, dies mit einem konventionelleren Javascript-Ansatz unter Verwendung eines IFFE zu tun :

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

Sehen Sie sich eine Demo an

JesperA
quelle
Was ist der Grund, die InstanceVariable hinzuzufügen ? Sie können einfach die Variable und die Funktionen direkt darunter platzieren App.Counter.
Fyaa
@fyaa Ja, Sie könnten aber die Variable und die Funktionen direkt unter App.Counter, aber ich denke, dieser Ansatz entspricht besser dem Singleton-Muster en.wikipedia.org/wiki/Singleton_pattern .
JesperA
0

Eine andere Möglichkeit ist die Verwendung von Symbolen in Ihrem Modul. Auf diese Weise können Sie Ihre Klasse schützen, auch wenn der Endbenutzer Ihrer API normales Javascript verwendet:

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

Verwendung:

var str:string = Singleton.instance.myFoo();

Wenn der Benutzer Ihre kompilierte API-JS-Datei verwendet, wird auch eine Fehlermeldung angezeigt, wenn er versucht, Ihre Klasse manuell zu instanziieren:

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

Kein reiner Singleton (Initialisierung kann nicht faul sein), aber ähnliches Muster mit Hilfe von namespaces.

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

Zugriff auf Objekt von Singleton:

MyClass.instance
Sergzach
quelle
0

Dies ist der einfachste Weg

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;
  }
}
Sorin Veștemean
quelle
-1
namespace MySingleton {
  interface IMySingleton {
      doSomething(): void;
  }
  class MySingleton implements IMySingleton {
      private usePrivate() { }
      doSomething() {
          this.usePrivate();
      }
  }
  export var Instance: IMySingleton = new MySingleton();
}

Auf diese Weise können wir eine Schnittstelle anwenden, anders als in Ryan Cavanaughs akzeptierter Antwort.

user487779
quelle
-1

Nachdem ich diesen Thread durchsucht und mit allen oben genannten Optionen herumgespielt hatte, entschied ich mich für einen Singleton, der mit geeigneten Konstruktoren erstellt werden kann:

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 */ }

}

Es würde eine Ersteinrichtung erfordern (in main.ts, oderindex.ts ) , die leicht von implementiert werden kann
new Singleton(/* PARAMS */)

Rufen Sie dann irgendwo in Ihrem Code einfach an Singleton.instnace. In diesem Fall workwürde ich anrufen , um fertig zu werdenSingleton.instance.work()

TheGeekZn
quelle
Warum sollte jemand eine Antwort ablehnen, ohne Verbesserungen zu kommentieren? Wir sind eine Community
TheGeekZn