Cách xác định Singleton trong TypeScript


128

Cách tốt nhất và thuận tiện nhất để triển khai mẫu Singleton cho một lớp trong TypeScript là gì? (Cả có và không có khởi tạo lười biếng).

Câu trả lời:


87

Các lớp Singleton trong TypeScript thường là một mẫu chống. Bạn chỉ có thể sử dụng không gian tên thay thế.

Mẫu singleton vô dụng

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

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

Không gian tên tương đương

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
bây giờ sẽ tốt hơn tại sao singleton được coi là một mô hình chống? xem xét cách tiếp cận này codebelt.com/typescript/typescript-singleton-potype
Victor

21
Tôi muốn biết tại sao Singletons trong TypeScript cũng được coi là một mô hình chống. Và nếu nó không có bất kỳ tham số constructor nào thì tại sao không export default new Singleton()?
emzero

23
Giải pháp không gian tên trông giống như một lớp tĩnh, không phải là một đơn vị
Mihai Răducanu

6
Nó hành xử giống nhau. Trong C #, bạn không thể vượt qua một lớp tĩnh xung quanh như thể nó là một giá trị (nghĩa là nó là một thể hiện của một lớp đơn), điều này làm hạn chế tính hữu dụng của nó. Trong TypeScript, bạn có thể truyền một không gian tên xung quanh như một thể hiện. Đó là lý do tại sao bạn không cần các lớp học đơn lẻ.
Ryan Cavanaugh

13
Một hạn chế của việc sử dụng một không gian tên như một singleton là nó không thể (theo hiểu biết của tôi) thực hiện một giao diện. Bạn có đồng ý với điều này @ryan
Gabe O'Leary

182

Kể từ TS 2.0, chúng tôi có khả năng xác định các công cụ sửa đổi khả năng hiển thị trên các hàm tạo , vì vậy bây giờ chúng tôi có thể thực hiện các singletons trong TypeScript giống như chúng ta đã quen với các ngôn ngữ khác.

Ví dụ đã cho:

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;

Cảm ơn bạn @Drenai đã chỉ ra rằng nếu bạn viết mã bằng cách sử dụng javascript được biên dịch thô, bạn sẽ không được bảo vệ chống lại nhiều lần khởi tạo, vì các ràng buộc của TS biến mất và hàm tạo sẽ không bị ẩn.


2
các nhà xây dựng có thể là riêng tư?
Chuyên gia muốn là

2
@Expertwannabe Điều này hiện có sẵn trong TS 2.0: github.com/Microsoft/TypeScript/wiki/ Kẻ
Alex

3
Đây là câu trả lời ưa thích của tôi! Cảm ơn bạn.
Martin Majewski

1
fyi, lý do cho nhiều trường hợp là độ phân giải mô-đun nút cản trở. Vì vậy, nếu bạn đang tạo một singleton trong nút, hãy đảm bảo rằng điều đó được xem xét. Cuối cùng tôi đã tạo một thư mục node_modules trong thư mục src của mình và đặt singleton vào đó.
webteckie

3
@KimchiMan Nếu dự án được sử dụng trong môi trường không có kiểu chữ, ví dụ: được nhập vào dự án JS, lớp sẽ không có bảo vệ chống lại việc khởi tạo thêm. Nó chỉ hoạt động trong môi trường TS thuần túy, nhưng không phát triển thư viện JS
Drenai

39

Cách tốt nhất tôi đã tìm thấy là:

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

}

Đây là cách bạn sử dụng nó:

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-potype/


3
Tại sao không làm cho các nhà xây dựng riêng tư?
Phil Mander

4
Tôi nghĩ rằng bài viết có khả năng có các nhà xây dựng tư nhân trong TS. github.com/Microsoft/TypeScript/issues/2341
Trevor

Tôi thích câu trả lời này. Các hàm tạo riêng rất tốt trong quá trình phát triển, nhưng nếu một mô đun TS được dịch mã được nhập vào môi trường JS, thì hàm tạo vẫn có thể được truy cập. Với cách tiếp cận này, nó gần như được bảo vệ chống lại việc sử dụng sai .... trừ khi SingletonClass ['_ dụ'] được đặt thành null / không xác định
Drenai

Liên kết bị hỏng. Tôi nghĩ rằng đây là liên kết thực tế: codebelt.github.io/blog/typescript/typescript-singleton-potype
El Asiduo

24

Cách tiếp cận sau đây tạo ra một lớp Singleton có thể được sử dụng một cách kỳ lạ như một lớp thông thường:

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

Mỗi new Singleton()hoạt động sẽ trả về cùng một ví dụ. Điều này tuy nhiên có thể gây bất ngờ bởi người dùng.

Ví dụ sau minh bạch hơn với người dùng nhưng yêu cầu sử dụng khác nhau:

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

Sử dụng: var obj = Singleton.getInstance();


1
Đây là cách nó nên được thực hiện. Nếu có 1 điều tôi không đồng ý với The Gang of Four trên - và có lẽ chỉ có 1 - đó là Mô hình Singleton. Có lẽ, C / ++ cản trở người ta thiết kế nó theo cách này. Nhưng nếu bạn hỏi tôi, mã khách hàng không nên biết hoặc quan tâm nếu đó là Singleton. Khách hàng vẫn nên thực hiện new Class(...)cú pháp.
Cody

16

Tôi ngạc nhiên khi không thấy mô hình sau đây, mà thực sự trông rất đơn giản.

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

export let Shout = new ShoutSingleton();

Sử dụng

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

Tôi đã nhận được thông báo lỗi sau: Biến đã xuất 'Shout' có hoặc đang sử dụng tên riêng 'ShoutSingleton'.
Twois

3
Bạn cũng phải xuất lớp 'ShoutSingleton' và lỗi sẽ biến mất.
Twois

Phải, tôi cũng ngạc nhiên. Tại sao thậm chí bận tâm với các lớp mặc dù? Singletons được cho là để che giấu hoạt động nội bộ của họ. Tại sao không chỉ xuất hàm helloWorld?
Oleg Dulin

xem vấn đề github này để biết thêm thông tin: github.com/Microsoft/TypeScript/issues/6307
Ore4444

5
Đoán không có gì ngăn cản người dùng chỉ tạo một Shoutlớp mới
dinois

7

Bạn có thể sử dụng các biểu thức lớp cho điều này (kể từ 1.6 tôi tin).

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

hoặc với tên nếu lớp của bạn cần truy cập vào loại bên trong

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

Một tùy chọn khác là sử dụng một lớp cục bộ bên trong singleton của bạn bằng cách sử dụng một số thành viên tĩnh

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

Thêm 6 dòng sau vào bất kỳ lớp nào để biến nó thành "Singleton".

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

Ví dụ kiểm tra:

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

[Chỉnh sửa]: Sử dụng câu trả lời của Alex nếu bạn muốn lấy ví dụ thông qua một thuộc tính thay vì một phương thức.


Điều gì xảy ra khi tôi làm new MySingleton(), nói 5 lần? mã của bạn có bảo lưu một thể hiện không?
Hlawuleka MAS

bạn không bao giờ nên sử dụng "mới": như Alex đã viết, hàm tạo phải là "riêng tư", ngăn không cho "MySingleton ()" mới. Cách sử dụng đúng là lấy một cá thể bằng MySingleton.getInstance (). AKAIK no constructor (như trong ví dụ của tôi) = một nhà xây dựng trống công cộng
Flavien Volken

"Bạn không bao giờ nên sử dụng" mới "- chính xác là quan điểm của tôi:". Nhưng làm thế nào để thực hiện của bạn ngăn cản tôi làm như vậy? Tôi không thấy bất cứ nơi nào bạn có một nhà xây dựng riêng trong lớp của bạn?
Hlawuleka MAS

@HlawulekaMAS Tôi đã không từ chối Tôi do đó tôi đã chỉnh sửa câu trả lời, lưu ý rằng một nhà xây dựng tư nhân là không thể trước TS 2.0 (tức là tại thời điểm tôi viết câu trả lời đầu tiên)
Flavien Volken

"tức là tại thời điểm tôi viết câu trả lời đầu tiên" - Có ý nghĩa. Mát mẻ.
Hlawuleka MAS

3

tôi nghĩ có thể sử dụng thuốc generic

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

cách sử dụng

bước 1

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

bước 2

    MapManager.Instance(MapManager).init();

3

Bạn cũng có thể sử dụng hàm Object .ze () . Nó đơn giản và dễ dàng:

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, điểm tốt về đóng băng (), nhưng hai lưu ý: (1) sau khi bạn đóng băng (singleton), bạn vẫn có thể sửa đổi singleton.data .. bạn không thể thêm thuộc tính khác (như data2), nhưng điểm là đóng băng ( ) không bị đóng băng sâu :) và (2) lớp Singleton của bạn cho phép tạo nhiều hơn một thể hiện (ví dụ obj1 = new Singleton (); obj2 = new Singleton ();), vì vậy Singleton của bạn không phải là Singleton :)
Dmitry Shevkoplyas

Nếu bạn nhập Lớp Singleton trong các tệp khác, bạn sẽ luôn nhận được cùng một thể hiện và dữ liệu trong 'dữ liệu' sẽ thống nhất giữa tất cả các lần nhập khác. Đó là cho tôi một singleton. Việc đóng băng trong việc đảm bảo rằng cá thể Singleton đã xuất chỉ được tạo một lần.
kenny

Kenny, (1) nếu bạn nhập lớp của mình trong các tệp khác, bạn sẽ không nhận được ví dụ. Bằng cách nhập, bạn chỉ cần đưa định nghĩa lớp trong phạm vi để bạn có thể tạo phiên bản mới. Sau đó, bạn có thể tạo> 1 thể hiện của lớp đã cho trong một tệp hoặc nhiều tệp, bất chấp toàn bộ mục đích của ý tưởng đơn lẻ. (2) Từ tài liệu: Phương thức Object.freeze () đóng băng một đối tượng. Một vật thể đông lạnh không còn có thể thay đổi được nữa; đóng băng một đối tượng ngăn chặn các thuộc tính mới được thêm vào nó. (cuối trích dẫn) Có nghĩa là đóng băng () không ngăn bạn tạo nhiều đối tượng.
Dmitry Shevkoplyas

Đúng, nhưng trong trường hợp này thì có, vì thành viên đã xuất đã là một thể hiện. Và ví dụ giữ dữ liệu. Nếu bạn cũng xuất một lớp xuất, bạn đã đúng và bạn có thể tạo nhiều thể hiện.
kenny

@kenny nếu bạn biết bạn sẽ xuất một thể hiện, tại sao phải bận tâm với if (!this.instance)hàm tạo? Có phải đó chỉ là một biện pháp phòng ngừa bổ sung trong trường hợp bạn đã tạo nhiều phiên bản trước khi xuất?
Alex

2

Tôi đã tìm thấy một phiên bản mới này mà trình biên dịch Typecript hoàn toàn ổn và tôi nghĩ là tốt hơn bởi vì nó không yêu cầu gọi một getInstance()phương thức liên tục.

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

Điều này không đi kèm với một nhược điểm khác nhau. Nếu bạn Singletonkhông có bất kỳ thuộc tính nào, thì trình biên dịch typecript sẽ phù hợp trừ khi bạn khởi tạo chúng với một giá trị. Đó là lý do tại sao tôi bao gồm một _expressthuộc tính trong lớp ví dụ của mình bởi vì trừ khi bạn khởi tạo nó với một giá trị, ngay cả khi bạn gán nó sau trong hàm tạo, typecript sẽ nghĩ rằng nó chưa được xác định. Điều này có thể được khắc phục bằng cách vô hiệu hóa chế độ nghiêm ngặt, nhưng tôi không muốn nếu có thể. Ngoài ra còn có một nhược điểm khác của phương thức này mà tôi nên chỉ ra, bởi vì hàm tạo thực sự được gọi, mỗi lần nó thực hiện một thể hiện khác được tạo ra về mặt kỹ thuật, nhưng không thể truy cập được. Về lý thuyết, điều này có thể gây rò rỉ bộ nhớ.


1

Đây có lẽ là quá trình dài nhất để tạo ra một singleton trong bản thảo, nhưng trong các ứng dụng lớn hơn là một trong những hoạt động tốt hơn đối với tôi.

Trước tiên, bạn cần một lớp Singleton, giả sử "./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; }
    }
}

Bây giờ hãy tưởng tượng bạn cần một bộ định tuyến singleton "./navulation/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!, Bây giờ khởi tạo hoặc nhập bất cứ nơi nào bạn cần:

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

import router = navigation.Router;

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

Điều tuyệt vời khi thực hiện singletons theo cách này là bạn vẫn sử dụng tất cả vẻ đẹp của các lớp bản thảo, nó mang lại cho bạn sự tinh tế tốt đẹp, logic singleton giữ tách biệt và dễ dàng loại bỏ nếu cần.


1

Giải pháp của tôi cho nó:

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
Trong constructor, thay vì ngoại lệ bạn có thể return Modal._instance. Bằng cách này, nếu bạn newlớp đó, bạn có được đối tượng hiện có, không phải là một đối tượng mới.
Mihai Răducanu

1

Trong Typecript, người ta không nhất thiết phải tuân theo new instance()phương pháp Singleton. Một lớp tĩnh không có hàm tạo được nhập cũng có thể hoạt động như nhau.

Xem xét:

export class YourSingleton {

   public static foo:bar;

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

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

Bạn có thể nhập lớp và tham khảo YourSingleton.doThing()trong bất kỳ lớp nào khác. Nhưng hãy nhớ, vì đây là một lớp tĩnh, nó không có hàm tạo nên tôi thường sử dụng một intialise()phương thức được gọi từ một lớp nhập Singleton:

import {YourSingleton} from 'singleton.ts';

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

Đừng quên rằng trong một lớp tĩnh, mọi phương thức và biến cũng cần phải tĩnh để thay vì thisbạn sẽ sử dụng tên lớp đầy đủ YourSingleton.


0

Đây là một cách khác để làm điều đó với cách tiếp cận javascript thông thường hơn bằng cách sử dụng 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();

Xem bản demo


Lý do để thêm Instancebiến là gì? Bạn coukd chỉ cần đặt biến và các hàm trực tiếp bên dưới App.Counter.
fyaa

@fyaa Có, bạn có thể nhưng biến và các hàm trực tiếp trong App.Count nhưng tôi nghĩ cách tiếp cận này phù hợp hơn với mẫu singleton en.wikipedia.org/wiki/Singleton_potype .
JesperA

0

Một tùy chọn khác là sử dụng Biểu tượng trong mô-đun của bạn. Bằng cách này, bạn có thể bảo vệ lớp của mình, nếu người dùng cuối cùng của API của bạn đang sử dụng Javascript bình thường:

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

Sử dụng:

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

Nếu người dùng đang sử dụng tệp js API đã biên dịch của bạn, cũng sẽ gặp lỗi nếu anh ta cố gắng khởi tạo thủ công lớp của bạn:

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

0

Không phải là một singleton thuần túy (khởi tạo có thể không lười biếng), nhưng mô hình tương tự với sự giúp đỡ của namespaces.

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

Truy cập vào đối tượng của Singleton:

MyClass.instance

0

Đây là cách đơn giản nhất

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

Bằng cách này, chúng ta có thể áp dụng một giao diện, không giống như trong câu trả lời được chấp nhận của Ryan Cavanaugh.


-1

Sau khi quét chủ đề này và chơi xung quanh với tất cả các tùy chọn ở trên - tôi đã giải quyết với một Singleton có thể được tạo bằng các hàm tạo thích hợp:

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

}

Nó sẽ yêu cầu thiết lập ban đầu (trong main.ts, hoặc index.ts), có thể dễ dàng thực hiện bằng
new Singleton(/* PARAMS */)

Sau đó, bất cứ nơi nào trong mã của bạn, chỉ cần gọi Singleton.instnace; trong trường hợp này, để workhoàn thành, tôi sẽ gọiSingleton.instance.work()


Tại sao một người nào đó hạ thấp câu trả lời mà không thực sự bình luận về các cải tiến? Chúng tôi là một cộng đồng
TheGeekZn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.