디자인 패턴 - 생성 패턴

hyuckhoon.ko·2024년 2월 29일
0

생성 패턴

인스턴스화의 복잡성 낮추기 위한 다양한 추상화 기법
-> 안전하고 일관된 객체 생성

1) 팩토리

어떤 클래스의 인스턴스를 만들지 서브클래스에서 판단한다.

파이썬에서는 팩토리 패턴이 별로 필요하지 않다고 한다.(p.363)
클래스, 함수 및 사용자 정의 객체 각각의 역할이 특별히 구분 돼 있지 않고, 파라미터나 할당에 사용될 수 있다.

https://cjw-awdsd.tistory.com/54

2) 싱글턴과 공유 상태

대부분의 경우 싱글턴은 필요하지 않거나 나쁜 선택이다.
객체 지향 소프트웨어를 위한 전역 변수의 한 형태이며 나쁜 습관이다. 단위 테스트도 어렵다.

싱글톤 패턴은 안티 패턴인가?

모듈

파이썬에서 모듈은 이미 싱글턴과 매우 유사하다. 여러 번 임포트하더라도 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

개선한 코드의 이점

  • 재사용성
  • 테스트 세분화
    - 전 : GitFetcher 통합 테스트
    • 후 : SharedAttribute 단위 테스트, GitFetcher 통합 테스트

borg 패턴(monostate)

같은 상태를 공유하는 인스턴스

핵심은 속성을 저장할 사전(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'}

DRY 원칙 준수하여 더 개선하기

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}"

빌더

복잡한 객체의 생성을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EB%B9%8C%EB%8D%94Builder-%ED%8C%A8%ED%84%B4-%EB%81%9D%ED%8C%90%EC%99%95-%EC%A0%95%EB%A6%AC

0개의 댓글