싱글톤

싱글톤이란 소프트웨어 전체에서 인스턴스를 하나만 생성해서 유지하는 디자인 패턴입니다. 인스턴스를 하나만 생성한다는 것에 주목 하면 됩니다. OOP언어에서는 특정 클래스에서 인스턴스를 생성할 때 처음 호출시에는 객체를 생성하지만 두번째 부터는 생성한 객체를 반환합니다. 다음과 같습니다.

class Animal { 
    private static Animal animal = null;

    public static Animal getInstance () {
        if (animal == null) {
            this.animal = new Animal();
        }
        return this.animal;
    }
}

Animal animal = Animal.getInstance();

자바스크립트에서는 객체 리터널로 선언된 객체가 싱글톤이라고 볼 수 있습니다. 해당 객체를 소프트웨어 전체에서 고유하게 유지하기만 하면됩니다.

const Animal = { };

싱글톤 객체 생성 패턴

싱글톤 객체를 생성하는 다양한 패턴이 있을 수 있습니다. 여기서는 총 세개의 패턴을 볼 예정입니다. 첫번째로 클로저를 이용한 단순한 형태, 두번째는 즉시 실행 함수를 이용한 방법, 그리고 세번째는 내부 클래스를 이용한 방법입니다.

클로저를 이용한 방법

let Animal = function () {
    const animal = this;
    Animal = function () {
        return animal;
    }

    this.run = function (){

    };
}

let o1 = new Animal();
let o2 = new Animal();

console.log(o1 === o2);

Animal 생성자가 처음 호출되면 내부에서 const animal = this; 클로저 변수를 선언합니다. 그리고 Animal 생성자 함수를 새로운 생성자 함수로 변경합니다. 해당 함수는 const animal 클로저 변수를 반환합니다.
객체 리터널만으로도 싱글톤 객체를 생성할 수 있으니, 해당 방법은 그다지 유용한거 같지는 않습니다. 다음으로 더 유용한 방법을 보겠습니다.

즉시 실행 함수를 이용한 방법

const Animal = (()=>{
    return {
        run () {

        }
    };
})();

Animal 함수는 run메소드를 가지는 객체를 반환하여 할당 됩니다. 이제 Animal은 그 자체로 싱글톤 객체 입니다.

Private Member

싱글톤 객체에서 private과 public 멤버들이 각각 필요할 수 있습니다. 예전에는 아래와 같이 앞에 _를 붙임으로써 이 멤버는 private라 규약을 정했던 적도 있습니다.

let Animal = {
    _privateMember1: function () {},
    _privateMember2: function () {},
    publicMember1: function () {},
    publicMember2: function () {}
};

_privateMember1, _privateMember2가 실제로는 접근이 가능하므로 진정한 private이라고 볼 수는 없습니다. 이는 다음과 같이 개선할 수 있습니다.

const Animal = (()=>{
    let privateMember1 = function () {

    }
    let privateMember2 = function () {
        
    }
    return {
        run () {

        },
        publicMember () {

        }
    };
})();

이제 privateMember1, privateMember2에는 접근이 불가능하고 run과 publicMember는 접근이 가능한 public이 됩니다.

내부 클래스 사용한 생성 패턴

이제 위 private 멤버 변수를 가지면서 내부 클래스를 통해 싱글톤 객체를 생성하는 방법을 알아보겠습니다.

const Animal = (()=>{
    let instance = null;
    const InnerClass = function () {
        
    }

    InnerClass.prototype.run = function () { };

    let privateMember1 = function () { };

    return {
        getInstance () {
            if (instance === null) {
                instance = new InnerClass();
            }
            return instance;
        }
    };
})();

const o1 = Animal.getInstance();
const o2 = Animal.getInstance();

console.log(o1 === o2);

네임스페이스

마지막으로 네임스페이스에 관해 알아봅니다. 지금은 모듈 시스템을 이용하여 파일 기반의 모듈로 로직을 구분하여 사용합니다. 이전에는 파일로 구분하여도 전역 영역을 오염시킬 염려가 많았습니다.
이 때, 네임스페이스를 사용해서 로직을 구분할 수 있습니다. 네임스페이스는 기능을 중점으로 나눌 수도 있으며 페이지 기반으로 나눌 수도 있습니다.
싱글톤 객체는 객체 하위에 또 다른 객체를 둠으로써 그 자체로 네임스페이스의 역할을 수행할 수 있습니다. 또한, 위에서도 설명했듯이, 객체 리터널을 생성함만으로도 싱글톤 객체를 생성할 수 있습니다.

  • 기능 중심으로 네임스페이스 생성하기
    let MyNameSpace = { };
    MyNameSpace.Common = { };
    MyNameSpace.Common.log = { };
    MyNameSpace.Common.Http = { };
    MyNameSpace.Error = { };
    
  • 페이지 중심으로 네임스페이스 생성하기
    let MyNameSpace = { };
    MyNameSpace.Page1 = { };
    MyNameSpace.Page2 = { };
    MyNameSpace.Page3 = { };
    

과거 널리 사용되는 SPA 프레임웍이 없을때, 자체 구현하던 SPA 프레임웍에서는 위와 같이 페이지 단위의 구분하여 사용하기도 했습니다.

ES2019로 싱글톤 만들기

class Animal {
  #animal = null;

  static getInstance() {
    if (this.animal == null) {
        this.animal = new Animal();
    }
    return this.animal
  }
}

console.log(Animal.getInstance() === Animal.getInstance());

ES2019에서는 # prefix 를 추가해 private class 필드를 선언할 수 있습니다다. 이를 이용해 위와 같이 작성할 수 있습니다.

다음에는 팩토리 패턴에 대해 알아봅니다.