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.