Bagaimana cara mendefinisikan Singleton dalam TypeScript

128

Apa cara terbaik dan paling nyaman untuk menerapkan pola Singleton untuk kelas di TypeScript? (Baik dengan dan tanpa inisialisasi malas).

maja
sumber

Jawaban:

87

Kelas singleton dalam TypeScript umumnya merupakan anti-pola. Anda bisa menggunakan ruang nama saja.

Pola tunggal yang tidak berguna

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

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

Setara dengan Namespace

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
sumber
55
akan lebih baik untuk sekarang mengapa singleton dianggap sebagai pola anti? pertimbangkan pendekatan ini codebelt.com/typescript/typescript-singleton-pattern
Victor
21
Saya ingin tahu mengapa Singletons dalam TypeScript dianggap sebagai anti-pola juga. Dan juga jika tidak memiliki parameter konstruktor mengapa tidak export default new Singleton()?
emzero
23
Solusi namespace lebih mirip kelas statis, bukan singleton
Mihai Răducanu
6
Berperilaku sama. Dalam C #, Anda tidak bisa melewatkan kelas statis di sekitar seolah-olah itu adalah nilai (yaitu seolah-olah itu adalah turunan dari kelas tunggal), yang membatasi kegunaannya. Di TypeScript, Anda bisa melewatkan namespace sekitar seperti contoh. Itu sebabnya Anda tidak perlu kelas tunggal.
Ryan Cavanaugh
13
Keterbatasan menggunakan namespace sebagai singleton adalah bahwa ia tidak dapat (setahu saya) mengimplementasikan antarmuka. Apakah Anda setuju dengan ini @ryan
Gabe O'Leary
182

Sejak TS 2.0, kami memiliki kemampuan untuk mendefinisikan pengubah visibilitas pada konstruktor , jadi sekarang kita dapat melakukan lajang dalam TypeScript sama seperti kita terbiasa dari bahasa lain.

Contoh yang diberikan:

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;

Terima kasih @Drenai telah menunjukkan bahwa jika Anda menulis kode menggunakan javascript mentah yang dikompilasi, Anda tidak akan memiliki perlindungan terhadap banyak instantiasi, karena kendala TS menghilang dan konstruktor tidak akan disembunyikan.

Alex
sumber
2
konstruktornya bisa pribadi?
Ahli ingin menjadi
2
@Expertwannabe Ini sekarang tersedia di TS 2.0: github.com/Microsoft/TypeScript/wiki/…
Alex
3
Ini jawaban pilihan saya! Terima kasih.
Martin Majewski
1
Fyi, alasan untuk beberapa contoh adalah resolusi modul simpul menghalangi. Jadi, jika Anda membuat singleton di node pastikan itu dipertimbangkan. Saya akhirnya membuat folder node_modules di bawah direktori src saya dan meletakkan singleton di sana.
webteckie
3
@ KimchiMan Jika proyek tersebut pernah digunakan dalam lingkungan non-naskah misalnya diimpor ke proyek JS, kelas tidak akan memiliki perlindungan terhadap instantiation lebih lanjut. Ini bekerja di lingkungan TS murni saja, tetapi tidak untuk pengembangan perpustakaan JS
Drenai
39

Cara terbaik yang saya temukan adalah:

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

}

Inilah cara Anda menggunakannya:

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
sumber
3
Mengapa tidak menjadikan konstruktor sebagai pribadi?
Phil Mander
4
Saya pikir pos mendahului kemampuan untuk memiliki konstruktor pribadi di TS. github.com/Microsoft/TypeScript/issues/2341
Trevor
Saya suka jawaban ini. Konstruktor pribadi sangat bagus selama pengembangan, tetapi jika modul TS yang dialihkan diimpor ke lingkungan JS, konstruktor masih dapat diakses. Dengan pendekatan ini hampir terlindungi dari penyalahgunaan .... kecuali SingletonClass ['_ instance'] diatur ke null / undefined
Drenai
Tautan rusak. Saya pikir ini adalah tautan yang sebenarnya: codebelt.github.io/blog/typescript/typescript-singleton-pattern
El Asiduo
24

Pendekatan berikut menciptakan kelas Singleton yang dapat digunakan dengan tepat seperti kelas konvensional:

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

Setiap new Singleton()operasi akan mengembalikan instance yang sama. Namun ini bisa tidak terduga oleh pengguna.

Contoh berikut lebih transparan bagi pengguna tetapi membutuhkan penggunaan yang berbeda:

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

Pemakaian: var obj = Singleton.getInstance();

maja
sumber
1
Ini adalah cara yang harus diterapkan. Jika ada 1 hal yang saya tidak setuju dengan The Gang of Four on - dan mungkin hanya 1 - Pola Singleton. Mungkin, C / ++ menghalangi seseorang untuk mendesainnya dengan cara ini. Tetapi jika Anda bertanya kepada saya, kode klien tidak boleh tahu atau peduli apakah itu Singleton. Klien harus tetap menerapkan new Class(...)sintaksis.
Cody
16

Saya terkejut tidak melihat pola berikut di sini, yang sebenarnya terlihat sangat sederhana.

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

export let Shout = new ShoutSingleton();

Pemakaian

import { Shout } from './shout';
Shout.helloWorld();
Romain Bruckert
sumber
Saya mendapat pesan kesalahan berikut: Variabel yang diekspor 'Shout' telah atau sedang menggunakan nama pribadi 'ShoutSingleton'.
Twois
3
Anda harus mengekspor kelas 'ShoutSingleton' juga, dan kesalahannya hilang.
Twois
Benar, saya juga terkejut. Kenapa malah repot-repot dengan kelas? Orang lajang seharusnya menyembunyikan pekerjaan internal mereka. Mengapa tidak hanya mengekspor fungsi helloWorld?
Oleg Dulin
lihat masalah github ini untuk info lebih lanjut: github.com/Microsoft/TypeScript/issues/6307
Ore4444
5
Kira tidak ada yang menghentikan pengguna dari hanya menciptakan Shoutkelas baru
dalore
7

Anda dapat menggunakan ekspresi kelas untuk ini (pada 1,6 saya percaya).

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

atau dengan nama jika kelas Anda perlu mengakses tipenya secara internal

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

Pilihan lain adalah menggunakan kelas lokal di dalam singleton Anda menggunakan beberapa anggota statis

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
sumber
7

Tambahkan 6 baris berikut ke kelas mana saja untuk menjadikannya "Singleton".

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

Contoh uji:

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

[Sunting]: Gunakan jawaban Alex jika Anda lebih suka mendapatkan instance melalui properti daripada metode.

Flavien Volken
sumber
Apa yang terjadi ketika saya melakukannya new MySingleton(), katakan 5 kali? apakah kode Anda memesan satu instance?
Hlawuleka MAS
Anda tidak boleh menggunakan "baru": seperti yang ditulis Alex, konstruktornya harus "pribadi", mencegah melakukan "MySingleton baru ()". Penggunaan yang tepat adalah untuk mendapatkan instance menggunakan MySingleton.getInstance (). AKAIK tanpa konstruktor (seperti dalam contoh saya) = konstruktor kosong publik
Flavien Volken
"Anda tidak boleh menggunakan" baru "- persis poin saya:". Tetapi bagaimana implementasi Anda mencegah saya melakukannya? Saya tidak melihat di mana pun Anda memiliki konstruktor pribadi di kelas Anda?
Hlawuleka MAS
@HlawulekaMAS Saya tidak ... Karena itu saya mengedit jawaban, perhatikan bahwa konstruktor pribadi tidak mungkin dilakukan sebelum TS 2.0 (yaitu pada saat saya menulis jawabannya terlebih dahulu)
Flavien Volken
"yaitu pada saat itu saya menulis jawabannya terlebih dahulu" - Masuk akal. Keren.
Hlawuleka MAS
3

Saya pikir mungkin menggunakan generik menjadi adonan

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

Cara Penggunaan

Langkah 1

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

Langkah 2

    MapManager.Instance(MapManager).init();
sanye
sumber
3

Anda juga dapat menggunakan fungsi Object.Freeze () . Sederhana dan mudah:

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
sumber
Kenny, poin bagus di freeze (), tapi ada dua catatan: (1) setelah freeze (singleton), Anda masih bisa memodifikasi singleton.data .. Anda tidak bisa menambahkan atribut lain (seperti data2), tetapi intinya adalah freeze ( ) tidak membeku dalam-dalam :) dan (2) kelas Singleton Anda memungkinkan untuk membuat lebih dari satu contoh (contoh obj1 = Singleton baru (); obj2 = Singleton baru ();), jadi Singleton Anda bukan Singleton
:)
Jika Anda mengimpor Kelas Singleton di file lain, Anda akan selalu mendapatkan contoh yang sama dan data dalam 'data' akan konsisten di antara semua impor lainnya. Bagi saya itu singleton. Bekukan untuk memastikan bahwa instance Singleton yang diekspor hanya dibuat satu kali.
kenny
Kenny, (1) jika Anda mengimpor kelas Anda di file lain Anda tidak akan mendapatkan contoh. Dengan mengimpor Anda hanya membawa definisi kelas dalam lingkup sehingga Anda dapat membuat instance baru. Kemudian Anda dapat membuat> 1 instance dari kelas yang diberikan baik itu dalam satu file atau beberapa file, yang menentang seluruh tujuan ide tunggal. (2) Dari dokumen: Metode Object.freeze () membekukan objek. Objek beku tidak lagi bisa diubah; membekukan objek mencegah properti baru tidak ditambahkan padanya. (akhir kutipan) Yang berarti membekukan () tidak menghentikan Anda dari membuat beberapa objek.
Dmitry Shevkoplyas
Benar, tetapi dalam kasus ini akan, karena anggota yang diekspor sudah menjadi contoh. Dan instance menyimpan data. Jika Anda menempatkan ekspor pada kelas juga, Anda benar dan Anda dapat membuat beberapa instance.
kenny
@kenny jika Anda tahu Anda akan mengekspor contoh, mengapa repot-repot dengan if (!this.instance)konstruktor? Apakah itu hanya tindakan pencegahan tambahan jika Anda membuat beberapa instance sebelum ekspor?
Alex
2

Saya telah menemukan versi baru dari hal ini bahwa kompiler Typecript benar-benar oke, dan saya pikir lebih baik karena tidak perlu memanggil getInstance()metode secara terus-menerus.

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

Ini memang datang dengan kelemahan yang berbeda. Jika Anda Singletonmemang memiliki properti apa pun, maka kompiler Scripteks akan melempar kecocokan kecuali Anda menginisialisasi mereka dengan nilai. Itu sebabnya saya menyertakan _expressproperti dalam kelas contoh saya karena kecuali Anda menginisialisasi dengan nilai, bahkan jika Anda menetapkannya nanti dalam konstruktor, Typcript akan berpikir itu belum didefinisikan. Ini bisa diperbaiki dengan menonaktifkan mode ketat, tapi saya lebih suka tidak jika memungkinkan. Ada juga kelemahan lain dari metode ini yang harus saya tunjukkan, karena konstruktor benar-benar dipanggil, setiap kali ia melakukan instance lain secara teknis dibuat, tetapi tidak dapat diakses. Secara teori, ini bisa menyebabkan kebocoran memori.

Eagerestwolf
sumber
1

Ini mungkin adalah proses terpanjang untuk membuat singleton dalam naskah, tetapi dalam aplikasi yang lebih besar adalah yang telah bekerja lebih baik untuk saya.

Pertama Anda membutuhkan kelas Singleton di, katakanlah, "./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; }
    }
}

Sekarang bayangkan Anda membutuhkan 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();
}

Bagus !, sekarang mulai atau impor di mana pun Anda butuhkan:

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

import router = navigation.Router;

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

Yang menyenangkan tentang melakukan lajang dengan cara ini adalah bahwa Anda masih menggunakan semua keindahan kelas naskah, ini memberi Anda kecerdasan yang bagus, logika tunggal tetap terpisah dan mudah untuk menghapus jika diperlukan.

a.guerrero.g87
sumber
1

Solusi saya untuk itu:

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
sumber
1
Di konstruktor, alih-alih pengecualian Anda bisa return Modal._instance. Dengan cara ini, jika Anda newkelas itu, Anda mendapatkan objek yang ada, bukan yang baru.
Mihai Răducanu
1

Dalam naskah, seseorang tidak harus mengikuti new instance()metodologi Singleton. Kelas statis impor, konstruktor-kurang dapat bekerja sama juga.

Mempertimbangkan:

export class YourSingleton {

   public static foo:bar;

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

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

Anda dapat mengimpor kelas dan merujuk ke YourSingleton.doThing()kelas lain mana pun. Tapi ingat, karena ini adalah kelas statis, ia tidak memiliki konstruktor jadi saya biasanya menggunakan intialise()metode yang dipanggil dari kelas yang mengimpor Singleton:

import {YourSingleton} from 'singleton.ts';

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

Jangan lupa bahwa di kelas statis, setiap metode dan variabel juga harus bersifat statis this Anda akan menggunakan nama kelas penuh YourSingleton.

Dominic Lee
sumber
0

Berikut ini cara lain untuk melakukannya dengan pendekatan javascript yang lebih konvensional menggunakan 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();

Lihat demo

JesperA
sumber
Apa alasan untuk menambahkan Instancevariabel? Anda hanya dapat menempatkan variabel dan fungsi langsung di bawah App.Counter.
fyaa
@fyaa Ya Anda bisa tetapi variabel dan fungsi langsung di bawah App.Counter tapi saya pikir pendekatan ini lebih sesuai dengan pola tunggal en.wikipedia.org/wiki/Singleton_pattern .
JesperA
0

Pilihan lain adalah menggunakan Simbol dalam modul Anda. Dengan cara ini Anda dapat melindungi kelas Anda, juga jika pengguna akhir API Anda menggunakan Javascript normal:

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

Pemakaian:

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

Jika pengguna menggunakan file kompilasi API js Anda, juga akan mendapatkan kesalahan jika ia mencoba untuk instantiate kelas Anda secara manual:

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

Bukan singleton murni (inisialisasi mungkin tidak malas), tetapi pola yang sama dengan bantuan namespaces.

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

Akses ke objek Singleton:

MyClass.instance
sergzach
sumber
0

Ini adalah cara paling sederhana

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

Dengan cara ini kita dapat menerapkan antarmuka, tidak seperti jawaban yang diterima Ryan Cavanaugh.

pengguna487779
sumber
-1

Setelah menjelajahi utas ini dan bermain-main dengan semua opsi di atas - Saya memilih Singleton yang dapat dibuat dengan konstruktor yang tepat:

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

}

Ini akan membutuhkan pengaturan awal (dalam main.ts, atau index.ts), yang dapat dengan mudah diimplementasikan oleh
new Singleton(/* PARAMS */)

Lalu, di mana pun dalam kode Anda, panggil saja Singleton.instnace; dalam hal ini, untuk workdiselesaikan, saya akan meneleponSingleton.instance.work()

TheGeekZn
sumber
Mengapa seseorang akan memilih jawaban tanpa benar-benar mengomentari perbaikan? Kami adalah komunitas
TheGeekZn