TypeScript'te Singleton nasıl tanımlanır


128

TypeScript'teki bir sınıf için bir Singleton kalıbı uygulamanın en iyi ve en uygun yolu nedir? (Tembel başlatma ile ve olmadan).

Yanıtlar:


87

TypeScript'teki Singleton sınıfları genellikle bir anti-modeldir. Bunun yerine ad alanlarını kullanabilirsiniz .

Yararsız singleton desen

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

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

Ad alanı eşdeğeri

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
Tekton neden anti model olarak kabul ediliyor şimdi güzel olurdu? bu yaklaşımı düşünün codebelt.com/typescript/typescript-singleton-pattern
Victor

21
TypeScript'teki Singletonların neden bir anti-model olarak kabul edildiğini bilmek istiyorum. Ayrıca herhangi bir kurucu parametresi yoksa neden olmasın export default new Singleton()?
emzero

23
İsim alanı çözümü daha çok durağan bir sınıfa benziyor, tek bir sınıfa değil
Mihai Răducanu

6
Aynı şekilde davranır. C # 'da, statik bir sınıfı, bir değermiş gibi (yani bir singleton sınıfının bir örneğiymiş gibi) geçiremezsiniz, bu da onun kullanışlılığını sınırlar. Daktilo, sen yapabilirsiniz bir örnek gibi bir ad etrafında geçmektedir. Bu yüzden singleton derslerine ihtiyacınız yok.
Ryan Cavanaugh

13
Bir ad alanını tekil olarak kullanmanın bir sınırlaması, (bildiğim kadarıyla) bir arabirim uygulayamamasıdır. Buna katılıyor musunuz @ryan
Gabe O'Leary

182

TS 2.0'dan beri , yapıcılarda görünürlük değiştiricileri tanımlama yeteneğine sahibiz , bu yüzden artık diğer dillerde alıştığımız gibi TypeScript'te tekli tonlar yapabiliriz.

Verilen örnek:

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, eğer derlenmiş ham javascript kullanarak kod yazarsanız, TS kısıtlamaları ortadan kalktığından ve kurucu gizlenmeyeceğinden, çoklu somutlaştırmaya karşı korumanız olmayacağını belirttiğiniz için teşekkür ederiz.


2
kurucu özel olabilir mi?
Uzman,

2
@Expertwannabe Bu artık TS 2.0'da mevcut: github.com/Microsoft/TypeScript/wiki/…
Alex

3
Bu benim tercih ettiğim cevap! Teşekkür ederim.
Martin Majewski

1
fyi, çoklu eşgörünümlerin nedeni düğüm modülü çözünürlüğünün önüne geçmesiydi. Bu nedenle, düğümde bir singleton oluşturuyorsanız, bunun dikkate alındığından emin olun. Src dizinim altında bir node_modules klasörü oluşturup singleton'ı oraya koydum.
webteckie

3
@KimchiMan Proje, yazı tipi olmayan bir ortamda kullanılırsa, örneğin bir JS projesine aktarılırsa, sınıfın daha fazla somutlaştırmaya karşı hiçbir koruması olmayacaktır. Yalnızca saf bir TS ortamında çalışır, ancak JS kitaplığı geliştirme için
çalışmaz

39

Bulduğum en iyi yol:

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

}

İşte onu nasıl kullanıyorsun:

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
Neden kurucuyu özel yapmıyorsunuz?
Phil Mander

4
Bence bu gönderi, TS'de özel inşaatçılara sahip olma becerisinden önce geliyor. github.com/Microsoft/TypeScript/issues/2341
Trevor

Bu cevabı beğendim. Özel kurucular geliştirme sırasında harikadır, ancak aktarılmış bir TS modülü bir JS ortamına aktarılırsa, kurucuya yine de erişilebilir. Bu yaklaşımla, yanlış kullanıma karşı neredeyse korunur .... SingletonClass ['_ instance'] null / undefined olarak ayarlanmadıkça
Drenai

Bağlantı koptu. Bence bu gerçek bağlantı: codebelt.github.io/blog/typescript/typescript-singleton-pattern
El Asiduo

24

Aşağıdaki yaklaşım, geleneksel bir sınıf gibi tam olarak kullanılabilen bir Singleton sınıfı oluşturur:

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

Her new Singleton()işlem aynı örneği döndürecektir. Ancak bu, kullanıcı tarafından beklenmedik olabilir.

Aşağıdaki örnek, kullanıcı için daha şeffaftır ancak farklı bir kullanım gerektirir:

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

Kullanımı: var obj = Singleton.getInstance();


1
Uygulanması gereken yol budur. The Gang of Four'a katılmadığım 1 şey varsa - ve muhtemelen sadece 1 - Singleton Pattern. Belki de C / ++ bu şekilde tasarlanmasını engelliyor. Ama bana sorarsanız, müşteri kodu bir Singleton olup olmadığını bilmemeli veya umursamamalıdır. İstemciler yine de new Class(...)sözdizimi uygulamalıdır .
Cody

16

Aslında çok basit görünen aşağıdaki kalıbı burada görmemek beni şaşırttı.

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

export let Shout = new ShoutSingleton();

kullanım

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

Aşağıdaki hata iletisini aldım: Dışa aktarılan 'Shout' değişkeni 'ShoutSingleton' özel adına sahip veya kullanıyor.
Twois

3
'ShoutSingleton' sınıfını da dışa aktarmanız gerekir ve hata ortadan kalkar.
Twois

Doğru, ben de şaşırdım. Neden sınıfla uğraşıyorsun ki? Tekillerin iç işleyişlerini gizlemeleri gerekiyor. Neden helloWorld işlevini dışa aktarmıyorsunuz?
Oleg Dulin

daha fazla bilgi için bu github sorununa bakın: github.com/Microsoft/TypeScript/issues/6307
Ore4444

5
Sanırım hiçbir şey kullanıcıları yeni bir Shoutsınıf oluşturmaktan
alıkoyamıyor

7

Bunun için sınıf ifadelerini kullanabilirsiniz (inanıyorum 1.6'dan itibaren).

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

veya sınıfınızın türüne dahili olarak erişmesi gerekiyorsa adı ile

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

Başka bir seçenek, bazı statik üyeler kullanarak tekliğinizin içinde yerel bir sınıf kullanmaktır.

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

Aşağıdaki 6 satırı herhangi bir sınıfa "Singleton" yapmak için ekleyin.

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

Test örneği:

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

[Düzenle]: Örneği bir yöntem yerine bir özellik aracılığıyla almayı tercih ediyorsanız, Alex yanıtını kullanın.


new MySingleton()5 kere yaparsam ne olur ? kodunuz tek bir örnek mi ayırıyor?
Hlawuleka MAS

asla "new" kullanmamalısınız: Alex'in yazdığı gibi, kurucu "özel" olmalı ve "new MySingleton ()" yapılmasını engellemelidir. Doğru kullanım MySingleton.getInstance () kullanarak bir örnek almaktır. AKAIK yapıcı yok (örneğimdeki gibi) = halka açık bir boş kurucu
Flavien Volken

"asla" yeni "kullanmamalısınız - tam olarak benim açımdan:". Peki uygulamanız bunu yapmamı nasıl engelliyor? Sınıfınızda özel bir kurucu olan hiçbir yerde göremiyorum?
Hlawuleka MAS

@HlawulekaMAS Yapmadım… Bu yüzden cevabı düzenledim, TS 2.0'dan önce özel bir kurucunun mümkün olmadığına dikkat edin (yani ilk cevabı yazdığım zaman)
Flavien Volken

"yani ilk cevabı yazdığım sırada" - Mantıklı. Güzel.
Hlawuleka MAS

3

Sanırım jenerikler daha meyilli olabilir

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

nasıl kullanılır

Aşama 1

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

Adım 2

    MapManager.Instance(MapManager).init();

3

Ayrıca Object.Freeze () işlevini de kullanabilirsiniz . Basit ve kolaydır:

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, freeze () konusunda iyi bir nokta, ancak iki not: (1) dondurduktan sonra (tekli), yine de singleton.data'yı değiştirebilirsiniz .. başka bir öznitelik ekleyemezsiniz (data2 gibi), ama önemli olan şu ki donma ( ) derin dondurucu değildir :) ve (2) Singleton sınıfınız birden fazla örnek oluşturmanıza izin verir (örnek obj1 = new Singleton (); obj2 = new Singleton ();), bu nedenle Singleton'ınız Singleton değildir
:)

Singleton Sınıfını başka dosyalarda içe aktarırsanız, her zaman aynı örneği alırsınız ve "veri" içindeki veriler diğer tüm içe aktarmalar arasında tutarlı olur. Bu benim için bir singleton. Dışa aktarılan Singleton örneğinin yalnızca bir kez oluşturulduğundan emin olmanın donması.
kenny

Kenny, (1) sınıfınızı başka dosyalara aktarırsanız örnek alamazsınız. İçe aktararak, yeni örnekler oluşturabilmeniz için kapsamda sınıf tanımını getirirsiniz. Daha sonra, tek bir dosyada veya birden çok dosyada belirli bir sınıfın 1'den fazla örneğini oluşturabilirsiniz, bu da tekli fikrin tüm amacına meydan okur. (2) Dokümanlardan: Object.freeze () yöntemi bir nesneyi dondurur. Dondurulmuş bir nesne artık değiştirilemez; Bir nesnenin dondurulması, ona yeni özelliklerin eklenmesini engeller. (alıntı sonu) Bu, freeze () 'in birden fazla nesne oluşturmanızı engellemediği anlamına gelir.
Dmitry Shevkoplyas

Doğru, ancak bu durumda olacak, çünkü dışa aktarılan üye zaten bir örnek. Ve örnek verileri saklar. Sınıfa da bir dışa aktarma koyarsanız, haklısınız ve birden çok örnek oluşturabilirsiniz.
kenny

@kenny, bir örneği dışa aktaracağınızı biliyorsanız, neden oluşturucu ile uğraşıyorsunuz if (!this.instance)? Bu, dışa aktarmadan önce birden çok örnek oluşturduğunuzda alacağınız ekstra bir önlem mi?
Alex

2

Bunun, Typescript derleyicisinin tamamen uygun olduğu yeni bir versiyonunu buldum ve bence daha iyi çünkü getInstance()sürekli bir metodu çağırmayı gerektirmiyor .

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

Bunun farklı bir dezavantajı var. Sizin Singletonherhangi bir özelliğiniz varsa, Typescript derleyicisi siz onları bir değerle başlatmadığınız sürece bir uyum oluşturacaktır. Bu yüzden _expressörnek sınıfıma bir özellik ekledim çünkü onu bir değerle başlatmadığınız sürece, daha sonra yapıcıda atasanız bile, Typescript tanımlanmadığını düşünecek. Bu, katı modu devre dışı bırakarak düzeltilebilir, ancak mümkünse yapmamayı tercih ederim. Bu yöntemin başka bir dezavantajı daha var, çünkü kurucu aslında çağrılıyor, her seferinde başka bir örnek teknik olarak yaratılıyor, ancak erişilebilir değil. Bu teorik olarak bellek sızıntılarına neden olabilir.


1

Bu muhtemelen daktiloda bir singleton yapmak için en uzun süreçtir, ancak daha büyük uygulamalarda benim için daha iyi sonuç veren süreçtir.

Önce "./utils/Singleton.ts" diyelim ki içinde bir Singleton sınıfına ihtiyacınız var :

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

Şimdi tek bir Yönlendirici "./navigation/Router.ts" dosyasına ihtiyacınız olduğunu hayal edin :

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

Güzel !, şimdi ihtiyacınız olan her yerde başlatın veya içe aktarın:

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

import router = navigation.Router;

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

Tekilleri bu şekilde yapmanın güzel yanı, hala daktilo sınıflarının tüm güzelliğini kullanıyor olmanızdır, size güzel bir zeka verir, tekli mantık bir şekilde ayrı kalır ve gerekirse çıkarması kolaydır.


1

Benim çözümüm:

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
Yapıcıda istisna yerine yapabilirsiniz return Modal._instance. Bu şekilde, eğer newo sınıfsanız, yeni bir nesneyi değil, mevcut nesneyi alırsınız.
Mihai Răducanu

1

Typescript'te mutlaka new instance()Singleton metodolojisini takip etmek zorunda değilsiniz . İçe aktarılan, yapıcısız statik bir sınıf da eşit şekilde çalışabilir.

Düşünmek:

export class YourSingleton {

   public static foo:bar;

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

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

Sınıfı içe aktarabilir ve YourSingleton.doThing()başka herhangi bir sınıfta başvurabilirsiniz . Ancak unutmayın, bu statik bir sınıf olduğundan, yapıcısı yoktur, bu nedenle genellikle intialise()Singleton'u içe aktaran bir sınıftan çağrılan bir yöntem kullanırım :

import {YourSingleton} from 'singleton.ts';

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

Statik bir sınıfta, her yöntemin ve değişkenin de statik olması gerektiğini unutmayın, böylece thistam sınıf adını kullanmak yerine sizin yerinize YourSingleton.


0

Bunu bir IFFE kullanarak daha geleneksel bir javascript yaklaşımıyla yapmanın bir başka yolu daha :

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

Bir demo izleyin


InstanceDeğişkeni eklemenin nedeni nedir ? Sadece değişkeni ve fonksiyonları doğrudan altına koyun App.Counter.
fyaa

@fyaa Evet, ancak değişken ve işlevler doğrudan App.Counter altında olabilir, ancak bu yaklaşımın en.wikipedia.org/wiki/Singleton_pattern tekil kalıbına daha iyi uyduğunu düşünüyorum .
JesperA

0

Diğer bir seçenek de modülünüzde Semboller kullanmaktır. Bu şekilde, API'nizin son kullanıcısı normal Javascript kullanıyorsa da sınıfınızı koruyabilirsiniz:

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

Kullanımı:

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

Kullanıcı derlenmiş API js dosyanızı kullanıyorsa, sınıfınızı manuel olarak başlatmaya çalıştığında da bir hata alır:

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

0

Saf bir singleton değil (başlatma tembel olmayabilir), ancak namespaces yardımı ile benzer model .

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

Singleton nesnesine erişim:

MyClass.instance

0

Bu en basit yol

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

Bu şekilde Ryan Cavanaugh'un kabul ettiği cevabın aksine bir arayüz uygulayabiliriz.


-1

Bu konuyu temizledikten ve yukarıdaki tüm seçeneklerle oynadıktan sonra - uygun kurucularla oluşturulabilen bir Singleton ile anlaştım:

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

}

Bir başlangıç ​​kurulumu (içinde main.ts, veya index.ts) gerektirir ve bu, tarafından kolayca uygulanabilir.
new Singleton(/* PARAMS */)

Ardından, kodunuzun herhangi bir yerinde arayın Singleton.instnace; bu durumda, bitirmek için workarardımSingleton.instance.work()


Neden birisi gerçekten iyileştirmeler hakkında yorum yapmadan bir yanıta olumsuz oy versin? Biz bir topluluğuz
TheGeekZn
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.