Come definire Singleton in TypeScript


128

Qual è il modo migliore e più conveniente per implementare un modello Singleton per una classe in TypeScript? (Sia con che senza inizializzazione pigra).

Risposte:


87

Le classi Singleton in TypeScript sono generalmente un anti-pattern. Puoi semplicemente usare gli spazi dei nomi invece.

Modello singleton inutile

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

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

Equivalente nello spazio dei nomi

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
sarebbe bello ora perché il singleton è considerato un anti pattern? considera questo approccio codebelt.com/typescript/typescript-singleton-pattern
Victor

21
Vorrei sapere perché anche i Singleton in TypeScript sono considerati un anti-pattern. E anche se non ha parametri di costruzione, perché no export default new Singleton()?
emzero,

23
La soluzione dello spazio dei nomi sembra più una classe statica, non un singleton
Mihai Răducanu,

6
Si comporta allo stesso modo. In C #, non è possibile passare una classe statica come se fosse un valore (ovvero come se fosse un'istanza di una classe singleton), il che ne limita l'utilità. In TypeScript, è possibile passare uno spazio dei nomi come un'istanza. Ecco perché non hai bisogno di lezioni individuali.
Ryan Cavanaugh,

13
Una limitazione dell'uso di uno spazio dei nomi come singleton è che non è in grado (a mia conoscenza) di implementare un'interfaccia. Saresti d'accordo con questo @ryan
Gabe O'Leary il

182

Da TS 2.0, abbiamo la possibilità di definire modificatori di visibilità sui costruttori , quindi ora possiamo creare singleton in TypeScript proprio come siamo abituati da altri linguaggi.

Esempio dato:

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;

Grazie @Drenai per aver sottolineato che se scrivi il codice utilizzando il javascript compilato non avrai protezione contro l'istanza multipla, poiché i vincoli di TS scompaiono e il costruttore non sarà nascosto.


2
il costruttore potrebbe essere privato?
esperto vuole essere

2
@Expertwannabe Questo è ora disponibile in TS 2.0: github.com/Microsoft/TypeScript/wiki/…
Alex

3
Questa è la mia risposta preferita! Grazie.
Martin Majewski,

1
a proposito, la ragione delle molteplici istanze è stata la risoluzione del modulo nodo. Pertanto, se si sta creando un singleton nel nodo, assicurarsi che sia considerato. Ho finito per creare una cartella node_modules nella mia directory src e inserendo il singleton.
webteckie,

3
@KimchiMan Se il progetto viene mai utilizzato in un ambiente non dattiloscritto, ad esempio importato in un progetto JS, la classe non avrà alcuna protezione da ulteriori istanze. Funziona solo in un ambiente TS puro, ma non per lo sviluppo di librerie JS
Drenai,

39

Il modo migliore che ho trovato è:

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

}

Ecco come lo usi:

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
Perché non rendere privato il costruttore?
Phil Mander,

4
Penso che il post preceda la possibilità di avere costruttori privati ​​in TS. github.com/Microsoft/TypeScript/issues/2341
Trevor,

Mi piace questa risposta. I costruttori privati ​​sono fantastici durante lo sviluppo, ma se un modulo TS transpiled viene importato in un ambiente JS, è possibile accedere al costruttore. Con questo approccio è quasi protetto contro l'uso improprio .... a meno che SingletonClass ['_ instance'] sia impostato su null / undefined
Drenai

Il collegamento è interrotto. Penso che questo sia il vero link: codebelt.github.io/blog/typescript/typescript-singleton-pattern
El Asiduo

24

Il seguente approccio crea una classe Singleton che può essere utilizzata esattamente come una classe convenzionale:

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

Ogni new Singleton()operazione restituirà la stessa istanza. Ciò può tuttavia essere inatteso dall'utente.

L'esempio seguente è più trasparente per l'utente ma richiede un uso diverso:

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

Uso: var obj = Singleton.getInstance();


1
Questo è il modo in cui dovrebbe essere implementato. Se c'è 1 cosa su cui non sono d'accordo con The Gang of Four - ed è probabilmente solo 1 - è The Singleton Pattern. Forse, C / ++ impedisce a uno di progettarlo in questo modo. Ma se me lo chiedi, il codice client non dovrebbe sapere o preoccuparsi se è un Singleton. I client dovrebbero comunque implementare la new Class(...)sintassi.
Cody

16

Sono sorpreso di non vedere il seguente schema qui, che in realtà sembra molto semplice.

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

export let Shout = new ShoutSingleton();

uso

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

Ho ricevuto il seguente messaggio di errore: La variabile esportata 'Shout' ha o sta usando il nome privato 'ShoutSingleton'.
Due è

3
Devi esportare anche la classe "ShoutSingleton" e l'errore scompare.
Due è

Bene, anche io sono sorpreso. Perché preoccuparsi della classe però? I single dovrebbero nascondere i loro meccanismi interni. Perché non limitarsi a esportare la funzione HelloWorld?
Oleg Dulin,

vedi questo numero di github per maggiori informazioni: github.com/Microsoft/TypeScript/issues/6307
Ore4444

5
Immagino che nulla stia impedendo agli utenti di creare una nuova Shoutclasse
dalore

7

Puoi usare espressioni di classe per questo (a partire dalla 1.6 credo).

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

o con il nome se la tua classe deve accedere al suo tipo internamente

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

Un'altra opzione è quella di utilizzare una classe locale all'interno del singleton usando alcuni membri statici

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

Aggiungi le seguenti 6 righe a qualsiasi classe per renderlo "Singleton".

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

Esempio di test:

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

[Modifica]: usa la risposta Alex se preferisci ottenere l'istanza attraverso una proprietà piuttosto che un metodo.


Cosa succede quando lo faccio new MySingleton(), dico 5 volte? il tuo codice riserva una singola istanza?
Hlawuleka MAS

non dovresti mai usare "nuovo": come ha scritto Alex, il costruttore dovrebbe essere "privato", impedendo di fare "nuovo MySingleton ()". L'uso corretto è quello di ottenere un'istanza utilizzando MySingleton.getInstance (). AKAIK no constructor (come nel mio esempio) = un costruttore pubblico vuoto
Flavien Volken

"non dovresti mai usare" nuovo "- esattamente il mio punto:". Ma in che modo la tua implementazione mi impedisce di farlo? Non vedo da nessuna parte dove hai un costruttore privato nella tua classe?
Hlawuleka MAS,

@HlawulekaMAS non ho ... Ho quindi modificato la risposta, nota che un costruttore privato non era possibile prima di TS 2.0 (cioè al momento in cui ho scritto la risposta prima)
Flavien Volken,

"Cioè al momento in cui ho scritto prima la risposta" - Ha senso. Freddo.
Hlawuleka MAS

3

penso che forse usare i generici sia una pastella

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

come usare

passo 1

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

passo 2

    MapManager.Instance(MapManager).init();

3

È inoltre possibile utilizzare la funzione Object.Freeze () . È semplice e facile:

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, buon punto su freeze (), ma due note: (1) dopo aver congelato (singleton), puoi ancora modificare singleton.data .. non puoi aggiungere altri attributi (come data2), ma il punto è che freeze ( ) non è un congelamento profondo :) e (2) la tua classe Singleton consente di creare più di un'istanza (esempio obj1 = new Singleton (); obj2 = new Singleton ();), quindi il tuo Singleton non è Singleton
:)

Se importi la classe Singleton in altri file otterrai sempre la stessa istanza e i dati in "dati" saranno coerenti tra tutte le altre importazioni. Questo è per me un singleton. Il blocco nell'assicurarsi che l'istanza Singleton esportata venga creata una sola volta.
Kenny,

Kenny, (1) se importi la tua classe in altri file non otterrai istanza. Con l'importazione stai semplicemente portando la definizione della classe nell'ambito in modo da poter creare nuove istanze. Quindi è possibile creare> 1 istanze di una determinata classe, sia in un file che in più file, il che sfida l'intero scopo dell'idea singleton. (2) Da docs: il metodo Object.freeze () congela un oggetto. Un oggetto congelato non può più essere modificato; il congelamento di un oggetto impedisce l'aggiunta di nuove proprietà ad esso. (fine del preventivo) Il che significa che freeze () non ti impedisce di creare più oggetti.
Dmitry Shevkoplyas il

Vero, ma in questo caso lo sarà, perché il membro esportato è già un'istanza. E l'istanza conserva i dati. Se metti anche un'esportazione nella classe, hai ragione e potresti creare più istanze.
Kenny,

@kenny se sai che esporterai un'istanza, perché preoccuparsi di if (!this.instance)nel costruttore? È solo un'ulteriore precauzione nel caso in cui tu abbia creato più istanze prima dell'esportazione?
Alex

2

Ho trovato una nuova versione di questo con cui il compilatore Typescript è totalmente a posto, e penso che sia meglio perché non richiede di chiamare un getInstance()metodo costantemente.

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

Ciò comporta uno svantaggio diverso. Se Singletonhai delle proprietà, allora il compilatore Typescript lancerà un adattamento a meno che tu non li inizializzi con un valore. Ecco perché ho incluso una _expressproprietà nella mia classe di esempio perché, a meno che non venga inizializzata con un valore, anche se la si assegna successivamente nel costruttore, Typescript penserà che non sia stata definita. Questo potrebbe essere risolto disabilitando la modalità rigorosa, ma preferisco non farlo se possibile. C'è anche un altro aspetto negativo di questo metodo che dovrei sottolineare, poiché il costruttore viene effettivamente chiamato, ogni volta che fa un'altra istanza viene tecnicamente creata, ma non accessibile. Ciò potrebbe, in teoria, causare perdite di memoria.


1

Questo è probabilmente il processo più lungo per creare un singleton in dattiloscritto, ma in applicazioni più grandi è quello che ha funzionato meglio per me.

Per prima cosa hai bisogno di una classe Singleton in, diciamo, "./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; }
    }
}

Ora immagina di aver bisogno di un singleton del router "./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 !, ora inizializzare o importare ovunque sia necessario:

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

import router = navigation.Router;

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

La cosa bella di fare i singleton in questo modo è che usi ancora tutta la bellezza delle classi dattiloscritte, ti dà un buon intellisense, la logica singleton rimane in qualche modo separata ed è facile da rimuovere se necessario.


1

La mia soluzione per questo:

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
Nel costruttore, invece dell'eccezione che puoi return Modal._instance. In questo modo, se sei newquella classe, ottieni l'oggetto esistente, non uno nuovo.
Mihai Răducanu,

1

In Typescript, non si deve necessariamente seguire la new instance()metodologia Singleton. Anche una classe statica importata senza costruttore può funzionare allo stesso modo.

Tener conto di:

export class YourSingleton {

   public static foo:bar;

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

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

È possibile importare la classe e fare riferimento YourSingleton.doThing()in qualsiasi altra classe. Ma ricorda, poiché questa è una classe statica, non ha un costruttore, quindi di solito uso un intialise()metodo chiamato da una classe che importa Singleton:

import {YourSingleton} from 'singleton.ts';

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

Non dimenticare che in una classe statica, ogni metodo e variabile deve anche essere statico, quindi invece di thisutilizzare il nome completo della classe YourSingleton.


0

Ecco un altro modo per farlo con un approccio javascript più convenzionale usando un 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();

Guarda una demo


Qual è il motivo per aggiungere la Instancevariabile? Puoi semplicemente mettere la variabile e le funzioni direttamente sotto App.Counter.
Fyaa,

@fyaa Sì, ma la variabile e le funzioni sono direttamente disponibili in App.Counter, ma penso che questo approccio sia conforme al modello singleton en.wikipedia.org/wiki/Singleton_pattern .
Jesper,

0

Un'altra opzione è utilizzare i simboli nel modulo. In questo modo puoi proteggere la tua classe, anche se l'utente finale della tua API sta usando il normale 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";
    }
}

Uso:

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

Se l'utente sta utilizzando il file js API compilato, inoltre riceverà un errore se tenta di creare un'istanza manualmente la classe:

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

0

Non un singolo singleton (l'inizializzazione può non essere pigra), ma un modello simile con l'aiuto di namespaces.

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

Accesso all'oggetto di Singleton:

MyClass.instance

0

Questo è il modo più semplice

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

In questo modo possiamo applicare un'interfaccia, diversamente dalla risposta accettata da Ryan Cavanaugh.


-1

Dopo aver setacciato questo thread e aver giocato con tutte le opzioni sopra, ho optato per un Singleton che può essere creato con costruttori appropriati:

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

}

Richiederebbe una configurazione iniziale (in main.ts, o index.ts), che può essere facilmente implementata da
new Singleton(/* PARAMS */)

Quindi, ovunque nel tuo codice, basta chiamare Singleton.instnace; in questo caso, per workfare ciò, chiamereiSingleton.instance.work()


Perché qualcuno dovrebbe sottovalutare una risposta senza effettivamente commentare i miglioramenti? Siamo una comunità
TheGeekZn,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.