TypeScript에서 싱글 톤을 정의하는 방법


128

TypeScript에서 클래스에 대한 싱글 톤 패턴을 구현하는 가장 쉽고 편리한 방법은 무엇입니까? (게으른 초기화 유무에 관계없이).

답변:


87

TypeScript의 싱글 톤 클래스는 일반적으로 안티 패턴입니다. 네임 스페이스를 간단하게 사용할 수 있습니다대신 를 .

쓸모없는 싱글 톤 패턴

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

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

네임 스페이스 동등

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
싱글 톤이 안티 패턴으로 간주되는 이유는 무엇입니까? 이 접근법을 고려하십시오 codebelt.com/typescript/typescript-singleton-pattern
Victor

21
TypeScript의 Singletons가 안티 패턴으로 간주되는 이유를 알고 싶습니다. 또한 생성자 매개 변수가없는 경우 왜 그렇지 export default new Singleton()않습니까?
emzero

23
네임 스페이스 솔루션은 싱글 톤이 아닌 정적 클래스처럼 보입니다.
Mihai Răducanu

6
동일하게 동작합니다. C #에서는 정적 클래스를 값처럼 (즉, 단일 클래스의 인스턴스 인 것처럼) 전달할 수 없으므로 유용성이 제한됩니다. TypeScript에서는 인스턴스처럼 네임 스페이스를 전달할 수 있습니다 . 그래서 싱글 톤 클래스가 필요하지 않습니다.
Ryan Cavanaugh

13
네임 스페이스를 싱글 톤으로 사용하는 것의 한계는 인터페이스를 구현할 수 없다는 것입니다. 이 @ryan에 동의 것
게이브 오리어리에게

182

TS 2.0부터 생성자에 가시성 수정자를 정의 할 수 있습니다. 할 수 있으므로 이제 다른 언어에서 사용하는 것처럼 TypeScript에서 싱글 톤을 수행 할 수 있습니다.

주어진 예 :

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;

원시 컴파일 된 자바 스크립트를 사용하여 코드를 작성하면 TS의 제약 조건이 사라지고 생성자가 숨겨지지 않기 때문에 다중 인스턴스화에 대한 보호 기능이 없다는 점에 대해 @Drenai에게 감사드립니다.


2
생성자가 비공개 일 수 있습니까?
전문가가

2
@Expertwannabe TS 2.0에서 사용할 수 있습니다 : github.com/Microsoft/TypeScript/wiki/…
Alex

3
이것은 내가 선호하는 답변입니다! 감사합니다.
Martin Majewski

1
참고로, 다중 인스턴스의 이유는 노드 모듈 확인이 방해를 받기 때문입니다. 따라서 노드에서 싱글 톤을 생성하는 경우 고려해야합니다. 내 src 디렉토리 아래에 node_modules 폴더를 만들고 싱글 톤을 거기에 넣었습니다.
webteckie

3
@KimchiMan 프로젝트가 비 유형 스크립트 환경 (예 : JS 프로젝트로 가져 오기)에서 사용되는 경우 클래스는 추가 인스턴스화를 방지하지 않습니다. 순수한 TS 환경에서만 작동하지만 JS 라이브러리 개발에는 적합하지 않습니다.
Drenai

39

내가 찾은 가장 좋은 방법은 다음과 같습니다.

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

}

사용 방법은 다음과 같습니다.

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
왜 생성자를 비공개로 만들지 않습니까?
Phil Mander

4
나는 게시물에 TS에 개인 생성자를 가질 수 있다고 생각합니다. github.com/Microsoft/TypeScript/issues/2341
Trevor

나는이 답변을 좋아한다. 개인 생성자는 개발 중에 훌륭하지만 변환 된 TS 모듈을 JS 환경으로 가져 오는 경우에도 여전히 생성자에 액세스 할 수 있습니다. 이 방법으로 오용으로부터 거의 보호됩니다 .... SingletonClass [ '_ instance']가 null / undefined로 설정되어 있지
않으면

링크가 끊어졌습니다. 나는 이것이 실제 링크라고 생각합니다 : codebelt.github.io/blog/typescript/typescript-singleton-pattern
El Asiduo

24

다음 접근 방식은 기존 클래스처럼 정확하게 사용할 수있는 Singleton 클래스를 만듭니다.

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

마다 new Singleton() 작업은 동일한 인스턴스를 반환합니다. 그러나 이것은 사용자가 예상하지 못한 것일 수 있습니다.

다음 예제는 사용자에게 더 투명하지만 다른 사용법이 필요합니다.

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

용법: var obj = Singleton.getInstance();


1
이것이 구현되어야하는 방법입니다. 이 난에 4의 갱에 동의 한 일이 있다면 - 그것은 아마 단지 1 - 그 싱글 톤 패턴. 아마도 C / ++ 은이 방법으로 디자인하지 못하게합니다. 그러나 나에게 묻는다면 클라이언트 코드는 Singleton인지 알거나 신경 쓰지 않아야합니다. 클라이언트는 여전히 new Class(...)구문을 구현해야 합니다.
코디

16

실제로 다음과 같은 패턴이 보이지 않는 것에 놀랐습니다. 실제로 매우 단순 해 보입니다.

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

export let Shout = new ShoutSingleton();

용법

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

다음과 같은 오류 메시지가 나타납니다. 내 보낸 변수 'Shout'에 개인 이름 'ShoutSingleton'이 있거나 사용 중입니다.
Twois

3
클래스 'ShoutSingleton'도 내 보내야하며 오류가 사라집니다.
Twois

그래, 나도 놀랐어. 그래도 왜 수업을 방해합니까? 싱글 톤은 내부 작업을 숨겨야합니다. 왜 함수 helloWorld를 내 보내지 않습니까?
Oleg Dulin

자세한 내용은이 github 문제를 참조하십시오 : github.com/Microsoft/TypeScript/issues/6307
Ore4444

5
Shout그래도 사용자가 새로운 클래스를 만드는 것을 막을 수있을 것
같지

7

이것을 위해 클래스 표현식을 사용할 수 있습니다 (1.6 생각대로).

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

또는 클래스가 내부적으로 유형에 액세스 해야하는 경우 이름

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

또 다른 옵션은 정적 멤버를 사용하여 싱글 톤 내부에서 로컬 클래스를 사용하는 것입니다

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

모든 클래스에 다음 6 줄을 추가하여 "Singleton"으로 만듭니다.

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

테스트 예 :

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

[편집] : 메서드 대신 속성을 통해 인스턴스를 가져 오려면 Alex 응답을 사용하십시오.


내가 new MySingleton()5 번 말하면 어떻게 되나요? 코드가 단일 인스턴스를 예약합니까?
Hlawuleka MAS

"new"를 사용해서는 안됩니다. Alex가 작성한 것처럼 생성자는 "private"이어야하며 "new MySingleton ()"을 수행 할 수 없습니다. 올바른 사용법은 MySingleton.getInstance ()를 사용하여 인스턴스를 얻는 것입니다. AKAIK 생성자 없음 (예에서와 같이) = 공개 빈 생성자
Flavien Volken

"당신은 절대로"신규 "를 사용해서는 안됩니다 – 정확히 내 요점 :". 그러나 당신의 구현이 어떻게 그렇게하지 못하게합니까? 수업에 개인 생성자가있는 곳은 보이지 않습니다.
Hlawuleka MAS

@HlawulekaMAS 나는하지 않았다 ... 그래서 저는 내가 개인 생성자 TS 2.0 전에 선수가없는, 메모를 대답을 편집 (즉, 시간에 내가 먼저 답을 썼다)
의 Flavien Volken에게

"즉, 대답을 먼저 썼을 때"-말이됩니다. 멋있는.
Hlawuleka MAS

3

어쩌면 제네릭을 사용하는 것이 어쩌면

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

사용하는 방법

1 단계

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

2 단계

    MapManager.Instance(MapManager).init();

3

Object.Freeze () 함수를 사용할 수도 있습니다 . 간단하고 쉽습니다.

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;

케니, freeze ()에 대한 좋은 지적이지만, 두 가지주의 사항 : (1) freeze (singleton) 후에도 singleton.data를 수정할 수 있습니다. data2와 같은 다른 속성을 추가 할 수는 없지만 포인트는 freeze ( )는 깊이 고정되지 않습니다 :) 및 (2) 클래스 Singleton을 사용하면 둘 이상의 인스턴스를 만들 수 있습니다 (예 : obj1 = new Singleton (); obj2 = new Singleton ();), 따라서 싱글 톤이 싱글 톤이 아님
:)

다른 파일에서 Singleton Class를 가져 오면 항상 동일한 인스턴스를 얻게되며 'data'의 데이터는 다른 모든 가져 오기간에 일관됩니다. 그것은 싱글 톤입니다. 내 보낸 싱글턴 인스턴스가 한 번만 생성되도록하는 동결.
kenny

케니, (1) 클래스를 다른 파일로 가져 오면 인스턴스를 얻지 못합니다. 가져 오기를 통해 단순히 클래스 정의를 범위 내로 가져 와서 새 인스턴스를 만들 수 있습니다. 그런 다음 하나의 파일 또는 여러 파일로 지정된 클래스의 인스턴스를 1 개 이상 만들 수 있으므로 단일 아이디어의 전체 목적을 무시합니다. (2) 문서에서 : Object.freeze () 메서드는 객체를 고정시킵니다. 고정 된 개체는 더 이상 변경할 수 없습니다. 객체를 고정하면 새로운 속성이 추가되지 않습니다. freeze ()는 여러 객체를 만드는 것을 막지 않습니다.
Dmitry Shevkoplyas 2016 년

그러나 내 보낸 멤버가 이미 인스턴스이므로이 경우에는 해당됩니다. 그리고 인스턴스는 데이터를 유지합니다. 수업에 수출을한다면, 당신이 옳고 여러 인스턴스를 만들 수 있습니다.
kenny

@ kenny 인스턴스를 내보낼 것이라는 것을 알고 있다면 왜 if (!this.instance)생성자에서 in을 귀찮게 합니까? 내보내기 전에 여러 인스턴스를 만든 경우 추가 예방 조치입니까?
Alex

2

Typescript 컴파일러가 완전히 괜찮은 새로운 버전을 발견했으며 getInstance()메소드를 지속적으로 호출 할 필요가 없기 때문에 더 좋습니다 .

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

이것은 다른 단점이 있습니다. 당신이 경우 Singleton어떤 특성이 있는가하면 값을 초기화하지 않는 한, 다음 타이프 라이터 컴파일러는 적합 발생합니다. 그렇기 _express때문에 예제 클래스에 속성을 포함시킨 이유 는 값으로 초기화하지 않으면 나중에 생성자에서 할당하더라도 Typescript가 정의되지 않은 것으로 생각하기 때문입니다. 엄격 모드를 사용 중지하여 문제를 해결할 수 있지만 가능하면 바람직하지 않습니다. 생성자가 실제로 호출되기 때문에 다른 인스턴스를 수행 할 때마다 기술적으로 생성되지만 액세스 할 수는 없으므로이 방법에 대한 또 다른 단점이 있습니다. 이론적으로 메모리 누수가 발생할 수 있습니다.


1

이것은 아마도 타입 스크립트에서 싱글 톤을 만드는 가장 긴 프로세스 일지 모르지만 더 큰 응용 프로그램에서는 나에게 더 잘 맞는 것입니다.

먼저 "./utils/Singleton.ts"에 Singleton 클래스가 필요합니다 .

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

이제 라우터 싱글 톤 "./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 !, 이제 필요한 곳을 초기화하거나 가져옵니다 :

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

import router = navigation.Router;

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

이 방법으로 싱글 톤을 수행하는 것에 대한 좋은 점은 여전히 ​​타입 스크립트 클래스의 모든 아름다움을 사용하고, 좋은 지능을 제공하며, 싱글 톤 논리는 어쨌든 분리되어 있으며 필요한 경우 쉽게 제거 할 수 있다는 것입니다.


1

그것에 대한 나의 해결책 :

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
생성자에서 예외 대신 할 수 있습니다 return Modal._instance. 이런 식으로, 당신이 new그 클래스라면, 새로운 객체가 아닌 기존 객체를 얻습니다.
Mihai Răducanu

1

Typescript에서 반드시 new instance()Singleton 방법론 을 따를 필요는 없습니다 . 가져온 생성자없는 정적 클래스도 동일하게 작동 할 수 있습니다.

치다:

export class YourSingleton {

   public static foo:bar;

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

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

클래스를 가져 와서 YourSingleton.doThing()다른 클래스에서 참조 할 수 있습니다 . 그러나 이것은 정적 클래스이므로 생성자가 없으므로 일반적으로 intialise()Singleton을 가져 오는 클래스에서 호출 되는 메서드를 사용합니다 .

import {YourSingleton} from 'singleton.ts';

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

정적 클래스에서는 모든 메소드와 변수도 정적이어야하므로 this전체 클래스 이름 대신을 사용해야합니다 YourSingleton.


0

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

데모 보기


Instance변수 를 추가하는 이유는 무엇입니까 ? 간단히 변수와 함수를 바로 아래에 넣습니다 App.Counter.
fyaa

@fyaa 예. App.Counter 바로 아래에서 변수와 함수를 사용할 수는 있지만이 접근법은 싱글 톤 패턴 en.wikipedia.org/wiki/Singleton_pattern을 더 잘 준수한다고 생각합니다 .
JesperA

0

다른 옵션은 모듈에서 기호를 사용하는 것입니다. 이렇게하면 API의 최종 사용자가 일반 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";
    }
}

용법:

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

사용자가 컴파일 된 API js 파일을 사용하는 경우 클래스를 수동으로 인스턴스화하려고하면 오류가 발생합니다.

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

0

순수한 싱글 톤이 아니고 (초기화는 게으르지 않을 수 있음), namespaces의 도움으로 비슷한 패턴입니다 .

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

싱글 톤의 객체에 접근 :

MyClass.instance

0

이것이 가장 간단한 방법입니다

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

이런 식으로 Ryan Cavanaugh의 대답과 달리 인터페이스를 적용 할 수 있습니다.


-1

이 스레드를 수색하고 위의 모든 옵션을 가지고 놀았을 때-적절한 생성자로 만들 수있는 Singleton으로 정착했습니다.

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

}

초기 설정 ( main.ts, 또는 index.ts)이 필요합니다.
new Singleton(/* PARAMS */)

그런 다음 코드의 어느 곳에서나 전화하십시오 Singleton.instnace. 이 경우에는 work전화를 걸어Singleton.instance.work()


실제로 개선에 대해 언급하지 않고 누군가가 답을 내리는 이유는 무엇입니까? 우리는 커뮤니티입니다
TheGeekZn
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.