소프트웨어 개발자/디자인패턴

[디자인패턴] 생성 패턴 - 싱글톤 (Singleton) 패턴

yubi5050 2024. 3. 30. 18:09

생성 (Creational) 패턴

다양한 상황에서의 객체의 생성 방식을 결정하는 디자인 패턴

 

주요 패턴으론 다음 사항들이 있다.

  • 팩토리 메서드 (Factory Method)
  • 추상 팩토리 (Abstract Factory)
  • 빌더(Builder)
  • 싱글톤(SingleTon) <- 이번 글에서 다룰 내용
  • 프로토타입(Prototype)  
  • 템플릿메서드(TemplateMethod)

 

싱글톤 패턴 이란?

싱글톤 패턴(Singleton Pattern) 이란 클래스에 하나의 객체(인스턴스)로 모든 인스턴스와 공유하는 패턴

싱글턴 객체는 맨 처음 요청될 때만 초기화를 진행되며, 해당 인스턴스에 대해 전역 접근 지점이 존재함

 

https://refactoring.guru/images/patterns/content/singleton/singleton-2x.png

 

객체 생성과 초기화

객체의 생성과 초기화에는 __new__ __init__ 이라는 메서드가 사용 

  • __new__ : 객체의 생성 과정시 호출
  • __init__ : 생성된 객체에 속성(property)을 추가 or 초기화 시 호출

__new__ 메소드에 대한 이해

사용자가 어떠한 목적을 가지고 클래스의 생성 과정에 관여하고 싶을 때, 직접 __new__ 메서드를 클래스에 추가함으로써 object 최상위 클래스의 __new__를 오버라이딩 하여 사용자의 __new__ 메소드가 호출되도록 한다. 만일 클래스에 __new__ 메소드를 오버라이딩 하여 선언하지 않으면, object 클래스의 __new__가 호출되어 객체가 생성됨

 

 

싱글톤 패턴을 사용하면 좋은 경우

  • 여러 서비스의 로그를 동일한 로깅객체를 사용해 기록하는 경우
  • 공유 리소스 관리 객체
  • 애플리케이션 구성 설정 (config)
  • 캐시
  • DB 연결 모듈에 단일 객체로 작업 수행하는 경우 (말만 그렇지. 사실상 성능이 안나옴, 잘못된 정보)

 

코드

__new__ 메소드 재 정의 후 기존 객체 존재시, 신규 생성 X

활용되는 객체 속성은 '_instance' 변수로 관리

class Singleton(object):
    # __new__, 이 __init__ 보다 먼저 실행됨
    def __new__(cls, *args, **kwargs): 
		# _instance 속성 : Singleton 객체
        if not hasattr(cls, "_instance"):
            print("__new__ is called")
            # 객체 생성 및 _instacne key로 바인딩
            cls._instance = super().__new__(cls)
        # _instance return
        return cls._instance

    def __init__(self, **kwargs):
        cls = type(self)
        # _init 속성 : Singleton 객체 존재 여부
        if not hasattr(cls, "_init"):
            print("__init__ is called")
            self.db_settings = kwargs
            cls._init = True


if __name__ == "__main__":
    # 최초 DB Setting (+ Connection)
    db = Singleton(db_name='mysql',db_port=3306)

    # DB가 연결된 Object Singleton 객체로 소환
    db_object1 = Singleton()
    db_object2 = Singleton()

    print(db)
    print(db_object1)
    print(db_object2)

 

출력 결과

최초 생성된 객체가 계속 사용되는 것을 확인 할 수 있다.

 

출력 결과

 

로그 관련

class Logger:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def log(self, message):
        print(f"[LOG]: {message}")


if __name__ == "__main__":
    logger = Logger()

    # logger가 연결된 Object Singleton 객체로 소환
    logger1 = Logger()
    logger2 = Logger()

    print(logger)
    print(logger1)
    print(logger2)

 

장단점

장점

  • 신규 인스턴스 생성 비용이 줄어듬

 

단점

  • 모듈간의 의존성이 높아짐 (이는 DI, Dependency Injection)을 통해 모듈간의 결합을 좀더 느슨하게 만들 수 있음
  • decoupling을 통해 모듈 간의 의존성을 떨어뜨릴 수 있다.

 

참고 문헌