파이썬 디자인 패턴, Design pattern in Python

Johnywhisky·2022년 11월 3일
0

TIL

목록 보기
9/9

정의

애플리케이션이 시작될 때, 클래스에 인스턴스 하나만 있도록 하면서(최초 한 번만 메모리를 할당) 해당 인스턴스에 대한 전역 접근 지점을 제공하는 디자인 패턴이다.

사용처

주로 공통된 객체를 여러 개 생성해서 사용해야 하는 상황에서 사용한다. 데이터베이스에서 커넥션풀, 스레드풀, 캐시, 로그 기록 객체 등이 있다.

패턴 구조

getInstance 메서드를 호출하는 것이 싱글턴 클래스를 가져올 수 있는 유일한 방법이어야 하며, 클래스 호출 시 이미 생성된 싱글턴 클래스가 있다면 해당 클래스를 반환하거나 반대의 경우에는 싱글턴 클래스를 인스턴스화(instantiating) 한다.

장단점

장점

클래스가 하나의 인스턴스 객체로만 존재하기 때문에 다른 클래스의 인스턴스들이 데이터를 공유, 절대적으로 한 개만 존재하는 것을 보증한다는 장점 등이 있다.

단점

반대로 객체 지향 설계 원칙 중에 개방-폐쇄 원칙이란 것이 존재한다. 싱글턴 클래스를 사용한 다른 클래스의 결합 도가 높아지게 되면, 유지보수가 힘들고 테스트도 원활하게 진행할 수 없는 문제점이 발생할 수 있다(모듈성 저하). 또한 싱글턴에 의존하는 클래스를 다른 콘텍스트에서 사용하려면 싱글턴도 다른 콘텍스트로 전달해야 한다. 이 또한 대부분은 유닛 테스트를 생성하는 동안 발생한다. 또한 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때, 인스턴스가 2개가 생성되는 문제도 발생할 수 있다.

코드

일반적인 싱글턴 패턴

  • Metaclass를 사용한 싱글턴
from collections import defaultdict


class SingletonMeta(type):
    _instances = defaultdict()

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        print(f"input kwargs: {kwargs} \n current class is {cls._instances[cls]}")
        return cls._instances[cls]


class SingletonWithMetaClass(metaclass=SingletonMeta):
    def __init__(self, *args, **kwargs):
        self._name: str = kwargs["name"]

    @property
    def name(self) -> str:
        return self._name

    @name.setter
    def name(self, name: str) -> None:
        setattr(self, "_name", name)

    @name.deleter
    def name(self):
        del self._name

    def __repr__(self) -> str:
        return f"this class is {self.name}'s class"


if __name__ == "__main__":

    p1 = SingletonWithMetaClass(name="John")
    p2 = SingletonWithMetaClass(name="Ming")

    print(p1.name, p2.name)
    print(id(p1), id(p2))
""" 결과:
input kwargs: {'name': 'John'}
input kwargs: {'name': 'Ming'}
# 클래스를 호출 할 때마다 input data는 제대로 입력되고 있다. 하지만
John John
# 기존 클래스의 attribute는 변경되지 않는다.
4566202784 4566202784
# class의 id도 동일한 주소값을 가지고 있다.
"""
  • 매직메서드(__new__)를 사용한 싱글턴
class SingletonWithNewMethod(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            print("__new__ is called", end="\n")
            cls._instance = super().__new__(cls)
            print(f"input kwargs: {kwargs}")
        return cls._instance

    def __init__(self, **kwargs):
        cls = type(self)
        if not hasattr(cls, "_init"):
            print("__init__ is called", end="\n")
            self._name = kwargs["name"]
            print(f"input kwargs: {kwargs}")
            cls._init = True

    @property
    def name(self) -> str:
        return self._name

    @name.setter
    def name(self, name: str) -> None:
        setattr(self, "_name", name)

    @name.deleter
    def name(self) -> None:
        del self._name

    def __repr__(self) -> str:
        return f"this class is {self.name}'s class"

if __name__ == "__main__":
	p3 = SingletonWithNewMethod(name="John")
    p4 = SingletonWithNewMethod(name="Ming")

    print(p3.name, p4.name)
    print(id(p3), id(p4))
""" 결과
__new__ is called
input kwargs: {'name': 'John'}
__init__ is called
input kwargs: {'name': 'John'}
# 최초 클래스 호출 시 __new__ 메소드가 작동하고, 이후에는 __init__이 작동한다.
John John
4566202832 4566202832
# 위 방식과 동일하게 최초 생성된 싱글턴 클래스는 내부 attribute값이 변경되지 않고, 동일한 id를 가지게 된다.
"""

스레드로부터 안전한 싱글턴

from threading import Lock, Thread


class SingletonMultiThread(type):
    _instances = defaultdict()
    _lock: Lock = Lock()

    def __call__(cls, *args, **kwargs):
        with cls._lock:
            if cls not in cls._instances:
                instance = super().__call__(*args, **kwargs)
                cls._instances[cls] = instance
        return cls._instances[cls]


class SingletonMultiThread(metaclass=SingletonMultiThread):
    _name = None

    def __init__(self, **kwargs) -> None:
        self._name: str | None = kwargs.get("name")

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name: str) -> None:
        setattr(self, "_name", name)

    @name.deleter
    def name(self) -> None:
        del self._name

    def __repr__(self) -> str:
        return f"this class is {self.name}'s class"

	@property
    def name(self):
    	return self._name


def test_singleton(name: str) -> None:
    smt = SingletonMultiThread(name=name)
    print(smt.name)


if __name__ == "__main__":
	p5 = Thread(target=test_singleton, args=["John"])
    p6 = Thread(target=test_singleton, args=["Ming"])
    p5.start()
    p6.start()
""" 결과
John
John
"""

출처

refactoring.guru

profile
안녕하세요 :) 1년 차 Pythonist 백엔드 개발자 윤서준입니다.

0개의 댓글