본문 바로가기
SW

GoF의 디자인 패턴(Design Patterns: Elements of Reusable Object-Oriented Software) - 3장 생성 패턴 :: 단일체(Singleton)

by 라꾸스떼(YR) 2020. 4. 22.
반응형

[단일체(Singleton) - 객체 생성]

<의도>

오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근성을 제공한다.

 

<동기>

클래스 자신이 자기의 유일한 인스턴스로 접근하는 방법을 자체적으로 관리한다. 이 클래스는 또 다른 인스턴스가 생성되지 않도록 할 수 있고, 클래스 자신이 그 인스턴스에 대한 접근 방법을 제공할 수 있다. 이를 가리켜 단일체 패턴이라고 한다.

 

<활용성>

-클래스의 인스턴스가 오직 하나여야 함을 보장하고, 잘 정의된 접근점(Access Point)으로 모든 사용자가 접근할 수 있도록 해야 할 때

-유일한 인스턴스가 서브클래싱으로 확장되어야 하며, 사용자는 코드의 수정없이 확장된 서브클래스의 인스턴스를 사용할 수 있어야 할 때

 

<구조>

-

 

<참여자>

-Singleton : Instance() 연산을 정의하여, 유일한 인스턴스로 접근할 수 있도록 한다. Instance() 연산은 클래스 연산이다.(C++ : 정적(static) 멤버 함수) 유일한 인스턴스를 생성하는 책임을 맡는다.

 

<협력 방법>

-사용자는 Singleton 클래스에 정의된 Instance() 연산을 통해서 유일하게 생성되는 단일체 인스턴스에 접근할 수 있따.

 

<결과>

1.유일하게 존재하는 인스턴스로의 접근을 통제한다. Singleton 클래스 자체가 인스턴스를 캡슐화하기 때문에, 이 클래스에서 사용자가 언제, 어떻게 이 인스턴스에 접근할 수 있는지 제어할 수 있다.

2.이름 공간(name space)을 좁힌다. 전역 변수를 정의하여 발생하는 디버깅의 어려움 등 문제를 없앤다.

3.연산 및 표현의 정제를 허용한다. Singleton 클래스는 상속될 수 있기 때문에, 이 상속된 서브클래스를 통해서 새로운 인스턴스를 만들 수 있다. 런타임에 필요한 클래스의 인스턴스를 써서 응용프로그램을 구성할 수도 있다.

4.인스턴스의 개수를 변경하기가 자유롭다. 마음이 바뀌어서 Singleton 클래스의 인스턴스가 하나 이상 존재할 수 있도록 변경해야 할 때도 있다. 응용프로그램이 사용하는 인스턴스가 다수여야 하 ㄹ때도 똑같은 방법을 쓸 수 있다. Singleton 클래스의 인스턴스에 접근할 수 있는 허용 범위를 결정하는 연산만 변경하면 된다.

5.클래스 연산을 사용하는 것보다 훨씬 유연한 방법이다. 단일체 패턴과 동일한 기능을 발휘하는 방법이 클래스 연산을 사용하는 것이다.(C++:정적 멤버 함수) C++의 정적 멤버 함수는 가상 함수가 아니므로, 서브클래스들이 이 연산을 오버라이드 할 수없다.

 

<구현>

고려사항

1.인스턴스가 유일해야 함을 보장한다. 단일체 패턴은 클래스의 인스턴스가 오로지 하나임을 만족해야 한다. 가장 일반적인 방법은 인스턴스를 생성하는 연산을 클래스 연산으로 만드는 것이다. 이 연산은 유일한 인스턴스를 관리할 변수에 접근해서 이 변수에 유일한 인스턴스로 초기화하고, 이 변수를 되돌려 준다. C++에서 클래스 연산을 정의하려면, 정적 멤버 함수를 정의해야 한다. Singleton 클래스에 Instance() 연산을 정적 멤버 함수로 정의하여 클래스 연산을 정의한다. 사용자는 반드시 Instance() 함수를 통해서만 인스턴스에 접근해야 한다.

C++를 사용한 구현에서 짚고 넘어갈 것은, 단일체를 전역 변수나 정적 객체로 정의하고 이를 자동 초기화하는 것만으로는 충분하지 않다는 것이다. (a) 정적 객체에서 인스턴스를 얻는 선언문이 프로그램 다른 부분에 존재하더라도 이를 확인하고 방지할 방법이 없다는 것이다. (b)정적 초기화 시점에 모든 단일체를 인스턴스화하기 위해 필요한 모든 정보가 없을 수도 있다. (c) C++에서는 전역 객체에 대한 생성자를 언제 호출하는지에 대한 명확한 순서를 정의하지 않는다.

전역 객체나 정적 객체의 또 다른 문제는 모든 단일체의 사용 여부와 상관없이 일단 모두 생성된다는 것이다. 그러나 정적 멤버 함수 기법을 이용하면 앞의 예에서처럼 실제 호출이 일어나는 시점에서 객체를 생성할 수도 있다.

2.Singleton 클래스를 서브클래싱한다. 서브클래스를 만드는 것이 중요한 게 아니라, 이 새로운 서브클래스의 유일한 인스턴스를 만들어 사용자가 일르 사용할 수 있도록 하는 것이다. 핵심은 Singleton의 인스턴스를 참조하는 변수가 이들 서브클래스의 인스턴스로 초기화되어야 한다는 것이다. Singleton의 서브클래스를 선택하는 또 다른 방법은 Instance() 연산의 구현을 슈퍼클래스가 아닌 서브클래스에서 하는 것이다. 연결 방법은 연결 시에 이미 단일체를 고정하기 때문에 런타임에 단일체 클래스를 선택하기가 힘들어진다. 유연한 방법은 단일체에 대한 레지스트리를 사용하는 것이다. Instance() 연산에 가능한 Singleton 클래스 집합을 정의하는 대신에 Singleton 클래스는 이 단일체 인스턴스를 레지스트리에 이름을 갖는 인스턴스로 등록한다. 레지스트리는 문자열로 정의된 이름을 해당 단일체 인스턴스로 대응 시켜 둔다. Instance() 연산에서 단일체가 필요할 때 레지스트리를 뒤져서 이름으로 해당 단일체를 찾아달라고 의뢰하면 레지스트리는 해당하는 단일체를 찾아서 돌려주는 것이다. 이런 방식을 취하면 Instance()연산이 모든 단일체 클래스와 인스턴스를 알 필요가 없다. Register() 연산은 이름과 Singleton 인스턴스를 등록한다. Lookup() 연산은 단일체 객체를 이름으로 찾는 기능의 연산이다. Singleton 클래스는 더는 단일체 인스턴스를 생성하는 책임이 없고, 그 대신에 시스템 전반에서 단일체 인스턴스로의 유일한 접근 창구 역할만 한다. 그러나 정적 객체를 사용한 이러한 접근법도 여전히 잠재적인 문제가 있다. 가능한 모든 Singleton 서브클래스의 인스턴스가 미리 생성되어 있어야만 레지스트리에 등록할 수 있다.

 

<예제 코드>

-

 

<잘 알려진 사용예>

메타클래스는 클래스의 클래스로서, 각 메타클래스는 인스턴스가 하나만 있다. 메타클래스는 이름이 없지만, 자기 자신의 유일한 인스턴스만 관리하며, 보통 다른 인스턴스는 생성하지 않는다.

 

<관련 패턴>

많은 패턴이 단일체 패턴으로 구현될 수 있다.

반응형

댓글