싱글턴 패턴 (Singleton Pattern)

임동혁 Ldhbenecia·2023년 4월 22일
0

Design Pattern

목록 보기
5/6
post-thumbnail

디자인 패턴: Singleton Pattern

Creation Design Pattern
글로벌하게 접근 가능한 단 한 개의 객체만을 허용하는 패턴
싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서
이 인스턴스에 대한 전역 접근(액세스) 지점을 제공하는 생성 디자인 패턴

  • 클래스에 대한 단일 객체 생성
  • 전역 객체 제공
  • 공유된 리소스에 대한 동시 접근 제어
  • 글로벌 액세스 지점을 제공하는, 단점이 거의 없는 검증된 패턴

Singleton Pattern (싱글턴 패턴)

  • 각각의 테마를 가진 여러가지 오브젝트들을 하나의 팩토리에서 만들고자 할 때
    이러한 오브젝트들을 만들어내는 패턴
  • 팩토리 자체를 추상화 시킴으로써 오브젝트를 쉽게 생성

코드 예시

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 클래스 인스턴스를 생성한다.
  • 이미 생성된 인스턴스가 있다면 재사용한다.
def __new__(cls):
    
    if not hasattr(cls, "instance"):
      print("create")
      cls.instance = super(Singleton, cls).__new__(cls)
    else:
      print("recycle")
    return cls.instance
  • 이 부분은 암기를 해두어도 좋다.
  • 이 코드가 보인다면 무조건 싱글턴 패턴이라는 것
  • 결과를 보면 알 수 있듯이 같은 레퍼런스(주소)를 참조한다는 것을 알 수 있다.

Lazy Instantiation

  • 싱글턴 패턴의 한 종류
  • 아직 필요하지 않는 시점에 실수로 객체를 미리 생성하는 것을 방지하기 위함
  • Lay 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() # 클래스를 초기화하여도 객체 생성 x
print(s._instance)
s1 = LazyInstantiation.getInstance() # 객체 생성
s2 = LazyInstantiation()
  • 클래스를 초기화하더라도 객체가 즉시 생성되지 않음
  • 아직 필요하지 않을 때 생성되는 것을 방지하기 위함
  • 꼭 필요할 떄 생성하도록 하기 위함

  1. 클래스를 초기화하여서 출력이되나 객체가 생성되지 않음
  2. s._instance 값이 그래서 None으로 출력
  3. 객체가 생성됨(getInstance) -> None으로 되어있어서 없으니 생성함
  4. 객체 생성된 이후의 문구 출력

Singleton과 Metaclass

  • Metaclass를 간단히 설명하면, 클래스를 만드는 클래스라고 할 수 있음
  • type은 객체의 클래스 종류를 알아낼 때 사용되지만, 클래스를 만들어낼 수도 있음
  • type을 상속받게 되면 메타클래스가 되며, 주로 클래스 동작을 제어할 때 사용됨
  • 메타클래스는 call 함수를 통항 객체 생성에 관한 제어를 할 수 있음

예시 1

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)

  • call 메소드는 이미 존재하는 클래스의 객체를 생성할 때 호출됨
  • int 클래스를 생성하면 MyInt 메타클래스의 call메소드가 호출됨
  • 객체 생성을 메타클래스가 제어한다는 의미

class int(metaclass=MyInt):를 통해 메타클래스를 지정함
생성시 MyInt의 call이 호출

  • *args = 인자를 typle로 만들어줌
  • **kwds = 딕셔너리로 만들어서 내부로 전달함

예시 2

class MetaSingleton(type):
  _instance = {}
  
  def __call__(cls, *args, **kwds):
    if cls not in cls._instance:
      cls._instance[cls] = super(MetaSingleton, cls).__call__(*args, **kwds)
      
    return cls._instance[cls]
  
class Box(metaclass = MetaSingleton):
  pass

b1 = Box() # create
b2 = Box() # recycle

print(b1==b2) # True
  • MetaSingleton(type)으로 type을 상속받게 되어 메타클래스가 됨
  • 생성자를 private으로 선언하는데 _instance 생성
  • call을 통해 생성여부 판단, 없으면 생성해서 리턴
  • Box 클래스(메타클래스)를 싱글톤으로 지정
  • 이러면 Box 클래스는 단 하나만 만들 수 있어짐, 싱글톤으로 지정되었기 때문
  • 그래서 print(b1==b2)를 하면 True가 출력됨

Singleton 패턴의 단점

  • 단일 책임 원칙(SRP)을 위반
  • 전역 변수의 값이 실수로 변경된 것을 모르고 사용될 수 있음
  • 같은 객체에 대한 여러 참조자가 생김
  • 전역 변수를 수정할 시 의도치 않게 다른 클래스에도 영향을 줄 수 있음
profile
지극히 평범한 공대생

0개의 댓글