1. unique instance임을 보증하는 것.
SingletonPattern의 경우도 일반 클래스와 마찬가지로 인스턴스를 생성하는 방법은 같다. 하지만 클래스는 늘 단일 인스턴스가 유지되도록 프로그래밍된다. 이를 구현하는 일반적인 방법은 인스턴스를 만드는 operation을 class operations으로 두는 것이다. (static member function이거나 class method) 이 operation은 unique instance를 가지고 있는 변수에 접근하며 이때 이 변수의 값 (인스턴스)를 리턴하기 전에 이 변수가 unique instance로 초기화 되어지는 것을 보장한다. 이러한 접근은 singleton이 처음 사용되어지 전에 만들어지고 초기화됨으로서 보장된다.
다음의 예를 보라. C++ 프로그래머는 Singleton class의 Instance operation을 static member function으로 정의한다. Singleton 또한 static member 변수인 _instance를 정의한다. _instance는 Singleton의 유일한 인스턴스를 가리키는 포인터이다.
Singleton class는 다음과 같이 선언된다.
~cpp
class Singleton {
public:
static Singleton* Instance ();
protected:
Singleton ();
private:
static Singleton* _instance;
};
대응되는 실제 구현부는 다음과 같다.
~cpp
Singleton* Singleton::_instance = 0;
Singleton* Singleton::Instance () {
if (_instance == 0) {
_instance = new Singleton;
}
return _instance;
}
클래스를 사용하는 Client는 singleton을 Instance operation을 통해 접근한다. _instance 는 0로 초기화되고, static member function 인 Instance는 단일 인스턴스 _Instance를 리턴한다. 만일 _instance가 0인 경우 unique instance로 초기화시키면서 리턴한다. Instance는 lazy-initalization을 이용한다. (Instance operation이 최초로 호출되어전까지는 리턴할 unique instance는 생성되지 않는다.)
생성자가 protected 임을 주목하라. client 가 직접 Singleton을 인스턴스화 하려고 하면 compile-time시 에러를 발새할 것이다. 생성자를 protected 로 둠으로서 늘 단일 인스턴스로 만들어지도록 보증해준다.
더 나아가, _instance 는 Singleton 객체의 포인터이므로, Instance member function은 이 포인터로 하여금 Singleton 의 subclass를 가리키도록 할 수 있다.
C++ 구현에 대해서는 생각해야 할 것이 더 있다. singleton 을 global이나 static 객체로 두고 난 뒤 자동 초기화되도록 놔두는 것으로 충분하지 않다. 이에 대해서는 3가지 이유가 있다.
- (a) static 객체의 유일한 인스턴스가 선언되어질 것이라고 보장할 수 없다.
- (b) 모든 singleton들이 static initialization time 대 인스턴스되기 위한 충분한 정보를 가지고 있지 않을수도 있다. singleton은 프로그램이 실행될 때 그러한 정보를 얻을 수 있다.
- (c) C++ 은 global 객체의 생성자가 translation unit를 통하면서 호출될때의 순서를 정의하지 않는다ES90. 이러한 사실은 singleton 들 간에는 어떠한 의존성도 존재할 수 없음을 의미한다. 만일 그럴 수 있다면, 에러를 피할 수 없다.
약간 첨언을 하면, global/static 객체의 접근은 singleton들이 사용되건 사용되지 않건 간에 모든 singleton이 만들어지도록 한다는 것이다. static member function 를 사용함으로서 이러한 모든 문제들을 피할 수 있다.
Smalltalk에서 unique instance를 리턴하는 functiond은 Singleton 클래스의 class method로 구현된다. 단일 인스턴스가 만들어지는 것을 보장하기 위해서 new operation을 override한다. The resulting Singleton class might have the following two class methods, where
SoleInstance is a class variable that is not used anywhere else:
~cpp
new
self error: 'cannot create new object'
default
SoleInstance isNil ifTrue: [SoleInstance := super new].
^ SoleInstance
2. Singleton class를 subclassing 하기 관련. 주된 주제는 클라이언트가 singleton 의 subclass를 이용할 수 있도록 subclass들의 unique instance를 설정하는 부분에 있다. 필수적으로, singleton 인스턴스를 참조하는 변수는 반드시 subclass의 인스턴스로 초기화되어져야 한다. 가장 단순한 기술은 Singleton의 Instance operation에 사용하기 원하는 singleton을 정해놓는 것이다. Sample Code에는 환경변수들을 가지고 이 기술을 어떻게 구현하는지 보여준다.
Singleton의 subclass를 선택하는 또 다른 방법은 Instance 를 Parent class에서 빼 낸뒤, (e.g,
MazeFactory) subclass 에 Instance를 구현하는 것이다. 이는 C++ 프로그래머로 하여금 link-time시에 singleton의 class를 결정하도록 해준다. (e.g, 각각 다른 구현부분을 포함하는 객체화일을 linking함으로써.)
이러한 link-approach 방법은 link-time때 singleton class 의 선택을 고정시켜버리므로, run-time시의 singleton class의 선택을 힘들게 한다. subclass를 선택하기 위한 조건문들 (switch-case 등등)은 프로그램을 더 유연하게 할 수 있지만, 그것 또한 이용가능한 singleton class들을 묶어버리게 된다. 이 두가지의 방법 다 그다지 유연한 방법은 아니다.
더욱더 유연한 접근 방법으로 registry of singletons 이 있다. 가능한 Singleton class들의 집합을 정의하는 Instance operation을 가지는 것 대신, Singleton class들을 잘 알려진 registry 에 그들의 singleton instance를 등록하는 것이다.
registry 는 string name 과 singletons 을 mapping 한다. singleton의 instance가 필요한 경우, registry에 string name으로 해당 singleton 을 요청한다. registry는 대응하는 singleton을 찾아서 (만일 존재한다면) 리턴한다. 이러한 접근방법은 모든 가능한 Singleton class들이나 instance들을 Instance operation이 알 필요가 없도록 한다. 필요한 것은 registry에 등록될 모든 Singleton class들을 위한 일반적인 interface이다.
~cpp
class Singleton {
public:
static void Register (const char* name, Singleton*);
static Singleton* Instance ();
protected:
static Singleton* Lookup (const char* name);
private:
static Singleton* _instance;
static List<NameSingletonPair>* _registry;
};
Register operation은 주어진 string name으로 Singleton instance를 등록한다. registry를 단순화시키기 위해 우리는
NameSingletonPair 객체의 리스트에 instance를 저장할 것이다. 각
NameSingletonPair는 name과 instance를 mapping한다. Lookup operation은 주어진 이름을 가지고 singleton을 찾는다. 우리는 다음의 코드에서 environment variable이 원하는 singleton의 이름을 명시하고 있음을 생각할 수 있다.
~cpp
Singleton* Singleton::Instance () {
if (_instance == 0) {
const char* singletonName = getenv("SINGLETON");
// user or environment supplies this at startup
_instance = Lookup (singletonName);
// Lookup returns 0 if there's no such singleton
}
return _instance;
}
어디에서 Singleton class들이 그들을 등록하는가? 한가지 가능성은 그들의 생성자에서다. 예를들어 singleton의 subclass인
MySingleton 은 다음과 같이 구현할 수 있다.
~cpp
MySingleton::MySingleton () {
// ...
Singleton::Register ("MySingleton", this);
}
물론, 코드 어디에선가 클래스를 인스턴스화하지 않으면 생성자는 호출되지 않을 것이다. C++에서는
MySingleton의 static instance를 정의함으로서 이 문제를 잘 해결할 수 있다. 예를 들어,
MySingleton 클래스의 구현부를 포함하는 화일에 다음과 같이 정의하면 된다.
~cpp
static MySingleton theSingleton;
더 이상 Singleton class 는 singleton 객체를 만들 책임이 없다. 그 대신 이제 Singleton 의 주된 책임은 시스템 내에서 선택한 singleton 객체를 접근가능하도록 해주는 것이다. static object approach는 여전히 단점이 존재한다. 모든 가능한 Singleton subclass들의 인스턴스들이 생성되어지던지, 그렇지 않으면 register되어서는 안된다는 것이다.