JS에서 싱글톤 구현하기 - 싱글톤 패턴, Object.freeze()

프엔개발쟈·2024년 2월 3일
0
post-thumbnail

싱글톤 패턴이란?

싱글톤 패턴은 한번만 만들 수 있는 클래스면서 전역에서 접근할 수 있는 클래스를 말한다.
single instance는 앱 내 어디에서든지 접근할 수 있기 때문에 전역상태를 관리하는데 유용하다

먼저 Counter라는 클래스를 만들면 다음과 같다

let counter = 0;

class Counter {
  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

이렇게 만들어진 Class의 인스턴스를 생성하면 다음과 같이 한 개가 아닌 여러개의 인스턴스를 만들 수 있고, counter1, counter2의 인스턴스는 동일하지도 않게 된다.

const counter1 = new Counter()
const counter2 = new Counter()

그래서 인스턴스를 생성할 때 인스턴스가 하나 존재한다면 만들지 못하게 에러를 뱉도록 추가해주어야 한다.

let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("You can only create one instance!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;

그리고 만들어진 instance를 Object.freeze() 해서 모듈을 export한다. freeze()를 통해 데이터를 변경하려는 실수등을 방지할 수 있다.

주의할 점

인스턴스를 한번만 만들 수 있게 제한하면 메모리 공간을 아낄 수 있지만, 사실 싱글톤은 anti-pattern으로 여겨진다.

java나 c++과 같은 객체지향 프로그래밍 언어에서는 js와 같이 바로 객체를 만들 수 없고, 객체를 만드는 클래스를 만들어야 한다. 그리고 만들어진 객체가 클래스의 인스턴스를 가지는데, 바로 이 클래스의 인스턴스가 자바스크립트에서의 instance가 된다.

사실 js에서 바로 객체를 만들 수 있기 때문에 위의 예제는 과하다 볼 수 있다. 다음과 같이 객체로 만들어도 똑같기 때문이다.

let count = 0;

const counter = {
  increment() {
    return ++count;
  },
  decrement() {
    return --count;
  }
};

Object.freeze(counter);
export { counter };

dependency 숨기기

counter.js 모듈을 import할때 해당 모듈이 싱글톤인지 아닌지 명확하지 않을 수 있다. 이 경우 실수로 싱글톤의 value 를 변경할 수도 있는데 이런식으로 superCounter를 만들어서 dependency를 숨길 수도 있다

import Counter from ".counter";

export default class superCounter {
  
  constructor() {
    this.count = 0;
  }
 
  increment(){
   Counter.increment();
    return (this.count+=100);
  }
  //...
}

Object.freeze()의 한계

Object.freeze()는 객체를 불변하게 만들고 싶을때 쓸 수 있지만, 얕은 동결이기 때문에, 중첩된 객체까지 동결하게 만들고 싶다면 다음과 같이 함수를 구현하여 동결해야 한다.

function deepFreeze(object) {
  // 객체에 정의된 속성명을 추출
  var propNames = Object.getOwnPropertyNames(object);

  // 스스로를 동결하기 전에 속성을 동결

  for (let name of propNames) {
    let value = object[name];

    object[name] =
      value && typeof value === "object" ? deepFreeze(value) : value;
  }

  return Object.freeze(object);
}

출처:
https://www.patterns.dev/vanilla/singleton-pattern
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

0개의 댓글