발생일: 2009.12.24

문제:
독립적인 기능을 하는 자바스크립트 클래스를 하나 만들었다.
이 클래스를 싱글턴으로 사용하고 싶다.

또한, 일반적인 getInstance() 라는 스태틱 메서드를 사용하는 대신,
바로 생성자를 호출해서 객체를 만들도록(내부적으로 싱글턴 인스턴스를 리턴) 하려고 한다.

어떻게 하면 자바스크립트에서 생성자만으로 싱글턴을 구현할 수 있을까?


해결책:
한참을 고민해봤다.
이런 방법으로 구현해보면 어떨까?

클래스의 생성자에 대한 유효범위를 제한해두고,
그 유효범위 안에서 window 속성의 클래스 생성자(래퍼)를 다시 정의한다.
(일종의 프록시 패턴이라고 할 수도 있겠다)
window 속성의 클래스 생성자에서는 클래스의 유일한 인스턴스를 만들어 리턴하도록 싱글턴을 구현한다.
즉, 외부에서 클래스 생성자를 호출해서 객체를 만들 때에는,
window 속성의 생성자를 호출하게 되므로 생성자만으로 싱글턴을 구현할 수 있다.


아래 실제 구현 예제를 이해하기 위해 몇 가지 자바스크립트 언어에 대한 선수 지식이 필요하다.

javascript 언어 특징 보기




간단한 실제 구현 모습은 아래와 같다.

    (function() { // (a) 익명함수로 유효범위를 제한한다.
   
        // (b) window 속성의 생성자를 정의한다.
        // 외부에서 생성자를 호출할 때에는 이 생성자가 호출된다.
        window.Singleton = function(name) {
            // (d) 객체가 생성되어 있지 않을 경우, 새 객체를 _instance 에 담는다.
            if (_instance == null) {
                _instance = new Singleton(name);
            }
            return _instance;
        }
   
        // (c) 유일한 싱글턴 인스턴스
        var _instance = null;
       
        // (e) 실제 객체 생성자
        // 외부에서 이 생성자에 접근할 수 없다.
        var Singleton = function(name) {
            this.name = name;
        }
       
    })();


실제 객체의 생성자는 (e) 부분이다.

외부에서 이 객체의 생성자에 접근하지 못하도록 익명함수(anonymous function)로 생성자를 감싸준다.
이 부분이 (a) 부분이 되겠다.

(c) 와 같이 유일한 싱글턴 인스턴스를 담을 변수를 선언해준다. 전역변수와 같이 선언되어 있지만,
이 변수 역시 생성자와 같은 유효범위에 있기 때문에 외부에서는 접근하지 못한다.

이제 외부에서 이 객체를 생성할 수 있도록 (b)와 같이 window 속성에 같은 이름으로 생성자를 구현해준다.
window.Singleton 은 window 속성이기 때문에 외부에서도 호출이 가능하며,
window prefix 없이 바로 new Singleton() 과 같이 호출이 가능하다.
(외부에서 new Singleton(); 을 호출했을 때 (e) 부분의 실제 함수가 호출되는 게 아니다.)
또한, window.Singleton 객체가 유효범위 밖에서 _instance 를 참조할 수 있는 건, 클로저(closure) 때문이다.

window 속성의 객체 생성자(b)에서는 (d)에서와 같이 싱글턴 부분을 구현한다.
_instance 가 존재하지 않을 경우, 새 객체를 생성해 할당하고, 그렇지 않을 경우 이미 존재하는 객체를 리턴한다.

이와 같이 구현하면 외부에서 new Singleton() 을 호출했을 때,
(실제 객체의 wrapper 격인) window.Singleton() 생성자가 호출되며,
window.Singleton 생성자 내에서 싱글턴 객체를 리턴하게 된다.

아래와 같이 호출해보면, new Singleton() 을 통해 싱글턴 객체가 생성되는 것을 확인할 수 있다.

    // 여기서 호출되는 Singleton 생성자는 window.Singleton 이다.
    var s1 = new Singleton('aaa');
    alert(s1.name); // aaa 출력
   
    var s2 = new Singleton('bbb'); // 이미 생성된 객체가 리턴된다.
    alert(s2.name); // aaa 출력



다소 번거롭긴 하지만, 이 방법을 통해 실제 생성자에 대한 접근을 제한할 수 있고,
new 키워드를 통해 싱글턴 객체를 리턴하도록 구현할 수 있다.
(굳이 패턴으로 나누어 생각하려고 한다면, 싱글턴 + 프록시 패턴 정도겠다. )

일반적인 패턴처럼, getInstance() 를 통해 싱글턴 객체를 생성하고 싶을 경우,
같은 방법으로 익명함수 내에서 window.Singleton 안에 getInstance() 를 정의해주면 되겠다.

기타 다른 방법들도 있지만,
기존 생성된 객체를 유지하는 한도 내에서 유용하게 사용할 수 있을 것이라 생각한다.



* 참고:
검색해보니 싱글턴을 구현하는 다른 방법들에 대한 좋은 자료들이 많다.

    1. How to make a singleton in javascript
이 포스트에서는 static getInstance() 메서드를 통해 싱글턴 객체를 생성하는 방법에 대해 설명되어 있다.
가장 알아보기 쉽고 기본적인 방법이니 참고해보도록 하자.
이 포스트에서는 자바스크립트 모듈 패턴을 통해 싱글턴 객체 생성자를 구현했다.
constructor 이름을 통해 이미 만들어진 객체를 싱글턴으로 만드는 전역 메서드 구현에 대한 방법도 있다.
좋은 내용이 많으니 꼭 읽어보자.
약간 다른 방법. 이 포스트도 참고해보자.


그 외,자바스크립트의 유효범위와 클로저에 대해 잘 이해가 안될 경우, 아래 포스트를 참고하자.
저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2009.07.30

문제:
친구 홍이 얘기한다.

apache commons의 BeanUtils 에서 getSimpleProperty 메서드의 소스를 보면...

-- BeanUtils class
public static String getSimpleProperty(Object bean, String name)
        throws IllegalAccessException, InvocationTargetException,
        NoSuchMethodException {

    return BeanUtilsBean.getInstance().getSimpleProperty(bean, name);
}

근데 여기에서 왜 getSimpleProperty 를
static 으로 선언하지 않고, 굳이 BeanUtilsBean을 생성해서 싱글턴 방식으로 호출할까?

왜 그럴까....?


해결책:
'BeanUtils를 싱글턴으로 만들었다가 사용편의성을 위해 static으로 따로 만들었던 걸까' 라고...
우리끼리 추측하고 있다가 홍이 소스를 뜯어보고는-
BeanUtilsBean 클래스에 보면 왜 저렇게 처리했는지 주석으로 달아놓았다고 한다.

 * <p>Occasionally it is necessary to store data in "global" variables
 * (including uses of the Singleton pattern). In applications which have only
 * a single classloader such data can simply be stored as "static" members on
 * some class. When multiple classloaders are involved, however, this approach
 * can fail; in particular, this doesn't work when the code may be run within a
 * servlet container or a j2ee container, and the class on which the static
 * member is defined is loaded via a "shared" classloader that is visible to all
 * components running within the container. This class provides a mechanism for
 * associating data with a ClassLoader instance, which ensures that when the
 * code runs in such a container each component gets its own copy of the
 * "global" variable rather than unexpectedly sharing a single copy of the
 * variable with other components that happen to be running in the same
 * container at the same time (eg servlets or EJBs.)</p>

데이터를 전역적으로 저장하기 위해 스태틱 또는 싱글턴을 사용할 때에,
다중 클래스로더 환경에서 제대로 동작하지 않을 수 있어서 위와 같은 메커니즘으로 구성했다고 한다.

결국 static 이냐, singleton 이냐의 문제가 아니라
'다중 클래스로더 환경에서의 정상적인 데이터 공유'의 문제였던 건가보다.


그래서 검색을 하다 보니 싱글턴 패턴에 대해 심도있게(비기너라고 써있긴 하지만..) 잘 정리해 둔 블로그가 있다.


위 블로그에서 다중 클래스로더 환경에서 싱글턴이 제대로 동작할 수 있도록
클래스로더를 직접 지정하는 방식에 대해 이야기한 부분이 있다.
그래서 BeanUtilsBean 의 getInstance() 의 소스를 확인해보니,
아니나 다를까 비슷한 방법으로 클래스로더를 직접 지정하고 있다.


public synchronized Object get() {
    // synchronizing the whole method is a bit slower
    // but guarantees no subtle threading problems, and there's no
    // need to synchronize valueByClassLoader
   
    // make sure that the map is given a change to purge itself
    valueByClassLoader.isEmpty();
    try {
       
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if (contextClassLoader != null) {
           
            Object value = valueByClassLoader.get(contextClassLoader);
            if ((value == null)
            && !valueByClassLoader.containsKey(contextClassLoader)) {
                value = initialValue();
                valueByClassLoader.put(contextClassLoader, value);
            }
            return value;
           
        }
       
    } catch (SecurityException e) { /* SWALLOW - should we log this? */ }
   
    // if none or exception, return the globalValue
    if (!globalValueInitialized) {
        globalValue = initialValue();
        globalValueInitialized = true;
    }//else already set
    return globalValue;
}


그래서 그렇게 했나보다..... 라고는 하고 있는데,
아직 클래스로더나 다중 클래스로더 환경에 대한 이해가 부족해서 정확하게는 잘 모르겠다.


팁:
그 이전에 static 과 singleton 의 차이에 대해 깔끔하게 정리해둔 블로그가 있으니 참고하자.
저작자 표시 비영리 변경 금지
Posted by ohgyun