Singleton
싱글톤 패턴은 면접 때 "알고있는 디자인 패턴 있어요?" 라고 하면 잘 모르는 사람이라도 머리속에 "음.. 싱글톤패턴..?" 이라고 한번쯤 생각해보지 않았을까? 싶을 정도로 제일 유명한 패턴 중 하나라고 생각한다. 하지만 막상 설명해보려고 하면 "인스턴스가 하나임을 보장 받을 수 있는 패턴입니다"를 제외하고 제대로 설명하기 위해 내용을 정리해보고자 한다.
의도
클래스에서 만들 수 있는 인스턴스가 오직 하나일 경우에 이에 대한 접근은 어디에서든지 하나로만 통일하여 제공할 수 있도록 한다.
동기
어떤 클래스의 경우, 정확히 하나의 인스턴스만을 갖도록 하는 것이 중요하다. 예를 들어 시스템에 많은 프린터가 있더라도, 프린터 스풀은 오직 하나여야 하는 것처럼 말이다.
어떻게 하면 우리는 클래스의 인스턴스를 하나만 만들고, 이 인스턴스에 쉽게 접근할 수 있을까? 물론 전역 변수를 이용해서 이 객체로 접근하도록 하면 여러 개의 인스턴스를 만들 필요가 없게 된다.
그러나 이보다 더 좋은 방법은 클래스 자체가 자신의 유일한 인스턴스로의 접근 방법을 자체적으로 관리하게 하는 것이다. 즉 클래스가 자기 자신 말고는 다른 인스턴스가 생성될 수 없음을 보증할 수 있고, 클래스 자체가 인스턴스에 대한 접근 방법을 제공할 수 있게끔 구현 하는 것을 우리는 Singleton 패턴이라고 한다.
활용성
언제 Singleton 패턴을 사용해야 할까?
- 클래스의 인스턴스가 오직 하나여야 함을 보장하고, 잘 정의된 접근 방식에 의해서 모든 클라이언트가 접근할 수 있도록 해야할 때
- 유일하게 존재하는 인스턴스가 상속에 의해 확장되어야 할 때, 클라이언트 코드의 수정 없이 확장된 서브클래스의 인스턴스를 사용할 수 있어야 할때
참여 객체
Instance 오퍼레이션을 정의하여, 유일한 인스턴스로의 접근이 가능하도록 구현한다. Instance 오퍼레이션은 클래스 오퍼레이션으로, 클래스에서 만드는 모든 인스턴스에 걸쳐서 공유되는 함수이다. 유일한 인스턴스의 생성에 대한 책임을 지게 된다.
협력 방법
클라이언트는 싱글톤 클래스에 정의된 Instance() 오퍼레이션을 통해서 유일하게 생성되는 싱글톤 인스턴스에 접근할 수 있다.
구현 (Python)
방법 1. __new__ 생성자를 통한 싱글톤 구현
class Singleton(object):
def __new__(cls, *args, **kwargs):
"""
*args, **kwargs
가변인자 > *args는 튜플 형태, **kwargs는 key:value쌍의 Dictionary 형태.
"""
if not hasattr(cls, 'instance'):
print('create')
cls.instance = super(Singleton, cls, *args, **kwargs).__new__(cls, *args, **kwargs)
else:
print('recycle')
return cls.instance
if __name__ == '__main__':
s = Singleton()
print('싱글톤 객체생성', s)
s1 = Singleton()
print('싱글톤 객체생성', s)
실행 결과에서 살펴볼 수 있듯이, 두 개의 객체를 생성해도 동일한 instance를 가짐을 볼 수 있다.
__new__ 메서드를 오버라이딩에 객체를 생성한다. __new__ 메서드는 hasattr을 통해 cls객체가 instance 속성을 가지고 있는지 확인하여, cls.instance라는 어트리뷰트가 없는 경우 생성자를 호출해 객체를 생성한다.
방법2. Lazy Instantiation (게으른 초기화)
게으른 초기화는 싱글톤 패턴을 기반으로 초기화 하는 방식이다. 모듈을 Import할 때, 아직 필요하지 않은 시점에서 객체를 미리 생성하는 경우에 사용하는 방식으로, 인스턴스가 필요한 시점에서 생성하는 방법이다.
class Singleton(object):
__instance = None
def __init__(self):
if not Singleton.__instance:
print("No instance")
else:
print("already created instance", self.get_instance())
@classmethod
def get_instance(cls):
if not cls.__instance:
cls.__instance = Singleton()
print('Creating')
return cls.__instance
if __name__ == "__main__":
s = Singleton()
Singleton.get_instance()
print("Created")
s1 = Singleton()
결과
** 지금 생각해보면, 첫 회사에서 미국 출장을 갔을 때 기술 이사님이 HL7 엔진이 싱글톤 패턴으로 구현되어 있었다. HL7 엔진은 쉽게 설명하자면 병원의 각 검사 시스템(검체/영상/진단검사System 등..)의 정보를 모두 받아 통일된 언어로 변환하여 의료정보 시스템에 저장할 수 있도록 하는 시스템이었다. 다양한 클라이언트에서 접근해도 유일한 인스턴스로 접근하여 관리가 유용했던 것으로 기억한다.
- 유일하게 존재하는 인스턴스로의 접근을 통제할 수 있다. 싱글톤 클래스 자체가 인스턴스를 캡슐화하고 있기 때문에 이 클래서에서 클라이언트가 언제 어떻게 이 인스턴스에 접근할 수 있는지를 제어할 수 있다.
- 변수 영역을 줄인다. 싱글톤 패턴은 전역 변수보다 장점이 있는데, 그것은 전역 변수를 사용해서 변수 영역을 망치는 일을 없애 준다는 것이다. 즉, 전역 변수를 정의하여 발생하는 디버깅의 문제를 없애준다.
- 오퍼레이션의 정제를 가능하게 한다. 싱글톤 클래스는 상속될 수 있기 때문에 이 상속된 서브클래스를 통해서 새로운 인스턴스를 만들 수 있다.
- 인스턴스의 개수를 변경하기가 자유롭다. 싱글톤 클래스의 인스턴스가 하나 이상 존재할 수 있도록 변경해야 하는 경우도 있다. 이는 싱글톤 클래스의 인스턴스에 접근할 수 있는 허용 범위를 결정하는 오퍼레이션만 변경하면 된다.
GoF의 디자인패턴 참고
'Development > 디자인패턴' 카테고리의 다른 글
디자인 패턴을 이용하는 방법 (5) (0) | 2022.04.21 |
---|---|
디자인 패턴을 이용하는 방법 (4) (0) | 2022.04.20 |
디자인 패턴을 이용하는 방법 (3) (0) | 2022.04.19 |
디자인 패턴을 이용하는 방법 (2) (0) | 2022.04.18 |
디자인 패턴을 이용하는 방법 (1) (0) | 2022.04.14 |