인스턴스화의 복잡성 낮추기 위한 다양한 추상화 기법
-> 안전하고 일관된 객체 생성
어떤 클래스의 인스턴스를 만들지 서브클래스에서 판단한다.
파이썬에서는 팩토리 패턴이 별로 필요하지 않다고 한다.(p.363)
클래스, 함수 및 사용자 정의 객체 각각의 역할이 특별히 구분 돼 있지 않고, 파라미터나 할당에 사용될 수 있다.
https://cjw-awdsd.tistory.com/54
대부분의 경우 싱글턴은 필요하지 않거나 나쁜 선택이다.
객체 지향 소프트웨어를 위한 전역 변수의 한 형태이며 나쁜 습관이다. 단위 테스트도 어렵다.
파이썬에서 모듈은 이미 싱글턴과 매우 유사하다. 여러 번 임포트하더라도 sys.moduels에 로딩되는 것은 단 하나다. 이처럼 모듈은 고유한 객체를 만들자는 아이디어다.
단, 모듈이 완전히 싱글턴과 일치한다고 볼 순 없다.
싱글턴은 호출 시 항상 동일한 객체를 제공해야 한다.
파이썬 인터프리터가 여러 개의 None
, True
, False
를 가질 필요가 없다.
알려진 객체는 싱글턴인가?
예를 들어, None의 경우 알려진 객체다. 이 사실 외에 다른 정보가 필요없다. True, False도 마찬가지다.
싱글턴보다 여러 인스턴스에서 사용할 수있도록 데이터를 복제하는 것이 더 좋다.
모든 인스턴스에 하나의 속성만 공유될 필요가 있다면 클래스 변수를 사용하면 된다.
class GitFetcher:
_current_tag = None
def __init__(self, tag):
self.current_tag = tag
@property
def current_tag(self):
if self._current_tag is None:
raise AttributeError("tag가 초기화되지 않았음")
return self._current_tag
@current_tag.setter
def current_tag(self, new_tag):
self.__class__._current_tag = new_tag
def pull(self):
return self.current_tag
>> f1 = GitFetcher(0.1)
>> f2 = GitFetcher(0.2)
>> f1.current_tag = 0.3
>> f1.pull())
0.3
>> f2.pull())
0.3
디스크립터 적용하여 개선
class SharedAttribute:
def __init__(self, initial_value=None):
self.value = initial_value
self._name = None
def __get__(self, instance, owner):
if instance is None:
return self
if self.value is None:
raise AttributeError("f{self._name} 속성이 초기화 되지 않았음")
return self.value
def __set__(self, instance, new_value):
self.value = new_value
def __set_name__(self, owner, name):
self._name = name
class GitFetcher:
current_tag = SharedAttribute()
current_branch = SharedAttribute()
def __init__(self, tag, branch=None):
self.current_tag = tag
self.current_branch = branch
def pull(self):
return self.current_tag
개선한 코드의 이점
같은 상태를 공유하는 인스턴스
핵심은 속성을 저장할 사전(dict)을 클래스 속성으로 지정해야 한다는 것이다.
dict는 mutable 객체이므로 한 곳에서 사전을 업데이트하면 모든 객체에 동일하게 업데이트 되기 때문이다.
class BaseFetcher:
def __init__(self, source):
self.source = source
class TagFetcher(BaseFetcher):
_attributes = {}
def __init__(self, source):
self.__dict__ = self.__class__._attributes
super().__init__(source)
def pull(self):
return f"Tag = {self.source}"
class BranchFetcher(BaseFetcher):
_attributes = {}
def __init__(self, source):
self.__dict__ = self.__class__._attributes
super().__init__(source)
def pull(self):
return f"Branch = {self.source}"
>>> t1 = TagFetcher("A")
>>> t1._attributes
{'source': 'A'}
>>> t2 = TagFetcher("B")
>>> t2._attributes
{'source': 'B'}
>>> t1._attributes
{'source': 'B'}
class SharedAllMixin:
def __init__(self, *args, **kwargs):
try:
self.__class__.attributes
except AttributeError:
self.__class__.attributes = {}
self.__dict__ = self.__class__.attributes
super().__init(*args, **kwargs)
class BaseFetcher:
def __init__(self, source):
self.source = source
class TagFetcher(SharedAllMixin, BaseFetcher):
def pull(self):
return f"Tag = {self.source}"
class BranchFetcher(SharedAllMixin, BaseFetcher):
def pull(self):
return f"Branch = {self.source}"
복잡한 객체의 생성을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴