본문 바로가기
Design Pattern/Creational Design

[Design Pattern] (Creational) 싱글톤 패턴(Singleton Pattern)

by song.ift 2023. 3. 26.

디자인 패턴 유형

  • Creation Design Pattern
    • 객체의 생성에 관련된 패턴
    • 객체의 생성 로직을 숨김으로써 유연성을 향상시킨다.
  • Strutural Design Pattern
    • 클래스와 객체의 구성에 관련된 패턴
  • Behavioral Design Pattern
    • 객체와 객체 간의 통신에 관련된 패턴

 

[Creational Design Pattern] 싱글톤 패턴(Singleton Pattern)

싱글톤 패턴은 전체 시스템에서 클래스에 대한 인스턴스가 하나만 존재하도록 보장하는 '객체 생성' 패턴이다.

싱글톤 패턴을 사용하면 고정된 메모리 영역에 인스턴스 하나만을 사용하기 때문에 메모리 낭비를 방지할 수 있다. 또한, 싱글톤으로 만들어진 인스턴스는 전역이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉬워진다.

하지만, 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유한다면, 시스템의 결합도가 높아지기 때문에 주의해야 한다. 결합도가 높아지면 유지보수가 어려워진다.

 

구현

싱글톤을 구현할 수 있는 방법은 다양하다. 단순 전역 변수에 객체 리터럴로 생성하는 것도 싱글톤이라고 할 수 있다. 

var obj = {
	myprop: 'my value'
};
var obj2 = {
	myprop: 'my value'
};

obj === obj2; // false
obj == obj2; // false

동일한 키와 값을 똑같이 지정했더라도 참조하는 주소값이 다르기 때문에 obj와 obj2는 각각 유일하게 존재하는 서로 다른 객체이다.

하지만, 전역 변수를 사용하기 보다는 클래스 자신이 자기의 유일한 인스턴스로 접근하는 방법을 자체적으로 관리하게 하는 것이 더 좋은 방법이다. 이 방법을 통해 클래스 외부에서는 인스턴스를 생성하지 못하게 하고, 내부에는 단 하나의 인스턴스를 생성해 외부에서 그 인스턴스에 접근할 수 있는 방법을 제공할 수 있다.

싱글톤의 구현은 보통 다음 세 가지를 지킨다.

  • 외부 클래스로부터 인스턴스 생성을 차단한다.
  • 인스턴스에 직접 접근하는 것을 차단한다.
  • 인스턴스에 대한 접점을 제공한다.

 

클로저를 이용한 싱글톤

비공개 프로퍼티와 함수를 정의하려면 클로저(closure)를 사용해야 한다. 아래처럼 IIFE(즉시 실행 함수)로 비공개 변수를 가질 수 있게 만들어주면 된다.

let Singleton = (function() {
    let instance; // 비공개 변수, 메서드 정의
    
    function init() { // 싱글톤 객체를 리턴할 비공개 함수
        // 싱글톤 객체 정의
        return {
            // 공개 메서드 정의
            publicMethod : function() {
                return 'public method';
            },
            // 공개 프로퍼티 정의
            publicProp : 'public variable'
        }
    }
    
    // 공개 메서드인 getInstance() 를 정의한 객체.
    // 렉시컬 특성으로 인해 비공개 변수, 메서드에 접근 가능(클로저)
    return {
        getInstance : function() {
            if (!instance) instance = init(); // 없으면 객체 생성
            return instance;
        }
    }
})();

// 싱글톤 객체 생성하여 publicMethod 호출 가능해짐
let first = Singleton.getInstance();
console.log(first.publicMethod()); // public method

let second = Singleton.getInstance();
console.log(second.publicMethod()); // public method

console.log(first === second); // true

instance는 getInstance의 외부 렉시컬 환경에 숨겨져 있기 때문에 직접 접근할 수 없다. init의 리턴 값에 없는 변수 또한 모두 숨길 수 있다.

  • 위의 예제 코드에서 먼저 비공개 함수인 init()의 return 문에서 객체 리터럴로 정의되는 객체가 싱글톤 객체다.
  • 이 객체는 프로그램 전체에서 하나만 존재하게 된다. 외부에 공개되는 익명함수의 return 문에서는 싱글톤 객체를 구하는 공개 메서드인 getInstance 가 포함된 객체를 반환한다.
  • getInstance 메서드에서는 내부 변수로 정의된 instance 변수의 값을 확인해서 아직 싱글톤 객체가 생성되지 않았다고 판단하면 내부 함수인 init 를 호출하여 싱글톤 객체를 생성해서 반환하게 된다.
  • 그런 다음 instance 변수에 싱글톤 객체를 할당하게 된다.
  • 이렇게 일반적으로 싱글톤 패턴에서는 이미 객체가 생성됐는지 여부를 알려주는 instance와 같은 내부 변수가 필요하다.
  • 그리고 싱글톤 패턴에서는 내부 변수에 접근할 수 있는 객체를 반환하는 클로저를 이용해야 한다.
  • 위 코드에서는 변수의 렉시컬한 특성으로 인해 내부의 getInstance 함수에서 비공개 변수인 instance에 접근할 수 있다는 것과 getInstane() 호출이 끝나더라도 instance 값은 계속 유지되는 특성(클로저)을 이용해 publicMethod(), publicProp 이 포함된 객체를 유일하게 생성하게 된다.

그래서 singleton.getInstance 를 몇 번 호출하더라도 결과로 얻는 객체는 모두 동일한 싱글톤 객체를 가리키게 된다.

 

Class를 이용한 싱글톤

 클래스에 #를 사용해 private 클래스 필드를 선언할 수 있다. 따라서 다음과 같은 싱글톤 구현이 가능하다.

class Singleton {
    static #instance;
    
    constructor() {
        if (Singleton.#instance) return Singleton.#instance;
        this.phrase = "Hello World!";
        Singleton.#instance = this;
    }
}

let a = new Singleton();
let b = new Singleton();
console.log(a === b); // -> true

댓글