디자인 패턴 - 싱글톤(Singleton) 패턴

2020. 3. 2. 15:13웹/Javascript Pattern

설명

  • 싱글톤 패턴은 특정 클래스의 인스턴스를 오직 하나만 유지

  • 동일한 클래스를 사용하여 새로운 객체를 생성하면, 두번째 부터는 처음 만들어진 객체를 얻게 된다.

 

 

자바스크립트에서 싱글톤 패턴

  • 자바스크립트에는 클래스가 없고 오직 객체만 있다.

  • 새로우 객체를 만들면 실제로는 이 객체는 다른 어떤 객체와도 같지 않기 때문에 이미 싱글톤이다.

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

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

 

 

new 연산자를 사용한 싱글톤 패턴 구현

var uni = new Universe();
var uni2 = new Universe();

uni === uni2;  // true

 

설명

  • uni는 생성자가 처음 호출되었을 때만 생성,  이후 호출(uni2) 부터는 동일한 uni 객체가 반환된다.

  • 변수들은 동일한 객체를 가리키는 참조일 뿐.

  • 객체의 인스턴스인 this가 생성되면 Universe 생성자가 이를 캐시한 후, 그 다음번에 생성자가 호출되었을 때 캐시된 인스턴스를 반환

 

위와 같은 결과를 얻기 위한 고려할 점

1. 인스턴스를 저장하기 위해 전역 변수 선언?

   => 일반적인 원칙상 전역 변수 선언은 좋지 않다. 누군가가 전역 변수를 덮어 쓸수있기 때문

2. 생성자 스태틱 프로퍼티에 인스턴스 저장?

   => Universe.instance와 같은 프로퍼티에 인스턴스를 저장할 수 있다.
        하지만 instance 프로퍼티가 공개되기 때문에
외부 코드에서 값을 변경하면 인스턴스를 잃어버릴 수 있다는 단점

3. 인스턴스 클로저로 감싸는 방법?

   => 인스턴스를 비공개로 만들어 생성자 외부에서 수정할 수 없게 해준다. 추가적인 클로저가 필요한 단점

 

 

클로저에 인스턴스 저장

function Universe() {
    // 캐시된 인스턴스
    var instance= this;

    this.start_time = 0;
    this.bang = "Big";

    // 생성자 생성
    Universe = function() {
        return instance;
    };
}

// TEST
var uni = new Universe();
var uni2 = new Universe();
uni === uni2;   // true

 

설명

  • 원본 생성자가 최초 호출 시 this를 반환

  • 두번째 호출 이상 부터 재작성된 생성자 실행

  • 재작성된 생성자를 통해 비공개 instance 변수에 접근하여 instnace 반환

 

단점

  • 재작성된 함수는 재정의 시점 이전에 원본 생성자에 추가된 프로퍼티를 잃어버리는 점

  • Universe()의 프로토타입에 무언가를 추가해도 원본생성자로 생성된 인스턴스와 연결되지 않는다

// 프로토타입에 추가
Universe.prototype.nothing = true;

var uni = new Universe();

// 첫번째 객체 생성 이후, 다시 프로토타입에 추가
Universe.prototype.everything = true;

// 두번째 객체 생성
var uni2 = new Universe();

// TEST
uni.nothing;     // true
uni2.nothing;    // true
uni.everything;  // undefined
uni2.everything; // undefined

uni.constructor.name;          // Universe
uni.constructor === Universe;  // false

 

설명

  • uni.contructor가 더이상 Universe() 생성자와 같지 않는 이유는 uni.constructor가 재정의된 생성자가 아닌 원본생성자를 가리키고 있기 때문.

 

 

프로토타입/생성자 포인터 정상 동작 방안 1

function Universe() {
    // 캐시된 인스턴스
    var instance;

    // 생성자 재작성
    Universe = function() {
        return instance;
    };

    // prototype 프로퍼티 변경
    Universe.prototype = this;
    
    // instnace
    instance = new Universe();
    
    // 생성자 포인트 재지정
    instance.constructor = Universe;
    
    instance.start_time = 0;
    instance.bang = "Big";

    return instance;
}

// TEST
// 프로토타입에 추가
Universe.prototype.nothing = true;

var uni = new Universe();

// 첫번째 객체 생성 이후, 다시 프로토타입에 추가
Universe.prototype.everything = true;

// 두번째 객체 생성
var uni2 = new Universe();

uni === uni2;    // true

uni.nothing;     // true
uni2.nothing;    // true
uni.everything;  // true
uni2.everything; // true

uni.constructor.name;          // Universe
uni.constructor === Universe;  // true

 

 

프로토타입/생성자 포인터 정상 동작 방안 2

var Universe;

(function() {
    var instance;
    
    Universe = function Universe() {
        if (instance) {
            return instance;
        }
        instance = this;
        this.start_time = 0;
        this.bang = "Big";
    }
}());

 

설명

  • 생성자와 인스턴스를 즉시 실행함수로 감싼다.

  • 생성자가 최초호출 되면, 생성자는 객체를 생성하고 비공개 instance를 가리킨다

  • 두번째 호출부터는 단순히 비공개 변수를 반환

 

 

 

 

출처 : JavaScript Patterns