Singleton Pattern 개요
글로벌하게 접근가능한 단 한개의 객체만을 허용하는 패턴
- 클래스에 대한 단일 객체 생성
- 전역 객체 제공
- 공유된 리소스에 대한 동시 접근 제어
- 글로벌 액세스 지점을 제공하는, 단점이 거의 없는 검증된 패턴
생성자를 private로 선언하고, 객체를 초기화하는 static 함수를 만들어 구현할 수 있음.
첫 호출에 객체가 생성되고, 그 후 클래스는 동일한 객체를 계속 반환함.]
class Singleton(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
print('create')
cls.instance = super(Singleton,cls).__new__(cls)
else:
print('recycle')
return cls.instance
s1 = Singleton() #create
print(s1)
s2 = Singleton() #recycle
print(s1)
print(s1 is s2) #true
- 한 개의 Singleton 클래스
인스턴스를 생성한다
- 이미 생성된 인스턴스가 있다면 재사용한다.
Singleton Pattern : Lazy Instantiation
- Singleton 패턴의 한 종류
- 아직 필요하지 않은 시점에 실수로 객체를 미리 생성하는 경우를 방지하기 위함
- Lazy Instantiation은 인스턴스가 꼭 필요할 때 생성하도록 함
- 사용할 수 있는 리소스가 제한적인 상황일 때 객체가 꼭 필요한 시점에 생성
class LazyInstantiation:
_instance = None
def __init__(self):
if not LazyInstantiation._instance:
print('__init__method called but nothing is created')
else:
print('instance already created: ', self.getInstance())
@classmethod
def getInstance(cls):
if not cls._instance:
cls._instance = LazyInstantiation()
return cls._instance
s = LazyInstantiation() #클래스를 초기화 했으나 객체는 생성되지 않음
print(s._instance)
s1 = LazyInstantiation.getInstance() #객체가 생성됨
s2 = LazyInstantiation()
Singleton과 Metaclass
- Metaclass 란?
클래스를 만드는 클래스
- type은 객체의 클래스 종류를 알아낼 때에도 사용되지만, 클래스를 만들어낼 수도 있음
- type을 상속받게 되면 Meta 클래스가 됨. -> 주로 클래스의 동작을 제어할 때 사용됨
- 메타클래스는 __call__함수를 통해서 객체 생성에 관한 제어를 할 수 있음.
#Metaclass
class MyInt(type):
def __call__(cls, *args, **kwds):
print('myint ', args)
print('Now do whatever you want with these objects...')
return type.__call__(cls, *args, **kwds)
class int(metaclass=MyInt):
def __init__(self, x, y):
self.x = x
self.y = y
i = int(4,5)
- *args : 인자를 tuple로 만들어줌
- **kwds : 딕셔너리로 꾸려서 내부로 전달
- __call__ 메소드는 이미 존재하는 클래스의 객체를 생성할 때 호출되는 파이썬의 특수 메소드
- int클래스를 생성하면 MyInt 메타 클래스의 __call__ 메소드가 호출됨
- 객체 생성을 메타클래스가 제어한다는 의미임
-> 메타클래스가 클래스와 객체 생성을 제어한다면 싱글톤을 생성하는 용도로 사용할 수 있다는 의미
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwds):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwds)
return cls._instances[cls]
class Box(metaclass = MetaSingleton):
pass
b1 = Box() #생성
b2 = Box() #재사용
print(b1==b2)
Metaclass의 __call__함수 내부에 인스턴스 생성여부를 확인하는 로직이 있으며,
Metaclass에 의해 Singleton으로 지정된 Box 클래스의 인스턴스가 생성될 때
Metaclass의 __call 함수가 호출되면서 Box의 생성을 제어함 (Singleton 성질을 가지게 됨)
Monostate Singleton Pattern
Singleton Pattern의 정의는반드시 한개의 클래스 객체만 존재해야함
하지만, Alex Martelli는 상태를 공유하는 인스턴스가 필요하다고 주장함
-객체의 생성여부보다, 객체의 상태와 행위가 더 중요
“Monostate Singleton Pattern은 모든 객체가 같은 상태를 공유하는 패턴"
class MonoState:
__shared_state = {"1":"2"}
def _init__(self):
self.x = 1
self.__dict__ = self.__shared_state
pass
ms1 = MonoState()
ms2 = MonoState()
print(ms1)
print(ms2)
ms2.x = 10
print(ms1.x)
print(ms2.x)
모든 객체는 객체의 변수정보들을 담고 있는 딕셔너리가 존재
-> __dict__
__dict__는 클래스에 속한 변수들을 내부적으로 보관하는 딕셔너리 변수
Singleton Pattern 활용 사례
DB 활용하는 클라우드 서비스
클라우드 서비스 내 DB에 접근하는 여러개의 모듈이 있음
여러 개의 서비스가 한 개의 DB를 공유하고 있는 구조
외우기!
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwds):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwds)
return cls._instances[cls]
class Database(metaclass = MetaSingleton):
connection = None
def connect(self):
if self.connection is None:
self.connection = sqlite3.connect("db.sqlite3")
self.cursorobj = self.connection.cursor()
return self.cursorobj
db1 = Database().connect()
db2 = Database().connect()
print(db1)
print(db2)
파이썬에서 모든 모듈들은 싱글톤
파이썬의 import 방식 때문에 모든 모듈은 기본적으로 싱글톤임
파이썬의 작동방식 살펴보면
- 파이썬 모듈이 import 되었는지 확인
- import 되었다면 해당 개체를 반환하고, 안되었다면 import를 instance화 함
- 모듈은 import와 동시에 초기화되지만, 같은 모듈을 다시 import하면 초기화 X
- 그래서 한개의 객체만 유지하고 반환하는 싱글톤 방식임.
Singleton Pattern 의 단점
싱글톤 패턴은 효율적이지만 단점 존재
- 전역 변수의 값이 실수로 변경된 것을 모르고 애플리케이션에서 사용될 수 있음
- 같은 객체에 대한 여러 참조자가 생김
- 전역 변수에 종속적인 모든 클래스 간 상호관계가 복잡해질 수 있음.
전역 변수 수정이 필요하게 되면 의도치 않게 다른 클래스에도 영향을 줄 수 있음.