singleton-pattern
TL;DR⚑
싱글톤 패턴은 애플리케이션에서 하나의 클래스에 대해 단 하나의 인스턴스만 존재하도록 보장하는 패턴임. 이를 통해 전역적으로 접근 가능한 유일한 인스턴스를 제공함.
활용성⚑
- 시스템에서 유일해야 하는 인스턴스를 제공하고자 할 때 유용함 (예: 데이터베이스 연결, 설정 관리 객체).
- 전역적으로 접근할 필요가 있는 객체가 있을 때 사용됨 (예: 로깅 서비스, 캐시).
- 자원의 낭비를 방지하기 위해 객체를 단일 인스턴스로 제한하고자 할 때 적합함.
결과⚑
-
장점
- 단일 인스턴스를 보장할 수 있음.
- 전역 접근이 가능함.
- 자원 절약 효과가 있음.
-
단점
- 테스트가 어려워질 수 있음.
- 의존 관계가 숨겨질 수 있음.
- 멀티스레딩 환경에서 잘못 구현하면 문제가 발생할 수 있음.
단점에 대한 상세 노트
테스트가 어려워지는 이유⚑
- 싱글톤 패턴을 사용하면 클래스의 인스턴스가 하나만 존재하도록 강제됨.
- 이로 인해 전역적으로 접근 가능한 상태가 만들어지는데, 이 전역 인스턴스는 여러 테스트에서 공유될 수 있음.
- 예를 들어, 테스트 A에서 싱글톤 인스턴스의 상태를 변경한 후, 테스트 B에서도 같은 인스턴스를 사용하게 되면, B는 A의 영향을 받을 수 있음.
- 이렇게 되면 각 테스트가 독립적으로 동작하지 않고 서로 간섭하게 되어, 예측할 수 없는 테스트 결과가 발생할 수 있음.
- 특히, 테스트 실행 순서에 따라 결과가 달라지는 문제가 발생할 수 있어, 단위 테스트의 신뢰성이 떨어지게 됨.
의존 관계가 숨겨지는 이유⚑
- 싱글톤 패턴은 전역적인 접근을 가능하게 하기 때문에, 다른 클래스들이 싱글톤 인스턴스를 직접 참조하게 됨.
- 이 경우 의존 관계가 코드에서 명시적으로 드러나지 않음.
- 예를 들어, 클래스 A가 싱글톤 B를 사용한다고 가정하면, A는 B를 생성자나 메서드 매개변수로 받지 않고 전역적으로 접근하여 사용함.
- 이로 인해 A와 B 간의 의존성이 코드상에 명확하게 나타나지 않음. 의존성이 숨겨지면, 코드의 유연성이 떨어지고, 나중에 B를 교체하거나 확장하기 어려워짐. 이는 유지보수성과 테스트 가능성을 저하시킴.
- 또한, 싱글톤에 대한 의존성이 여러 클래스에 퍼져 있을 경우, 해당 싱글톤을 교체하거나 모킹(mocking)하는 것이 어려워지며, 전체 시스템의 결합도가 높아져 코드의 유연성과 재사용성이 낮아짐.
구현⚑
- Singleton: 인스턴스 생성을 제어하며, 하나의 인스턴스만 생성되도록 보장함.
- Client: 싱글톤 인스턴스에 접근하여 필요한 작업을 처리함.
예시 코드⚑
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
def __init__(self, value: str):
self.value = value
def business_logic(self):
return f"비즈니스 로직 수행: {self.value}"
if __name__ == "__main__":
s1 = Singleton("첫 번째 인스턴스")
s2 = Singleton("두 번째 인스턴스")
print(s1.business_logic()) # "첫 번째 인스턴스" 출력
print(s2.business_logic()) # "첫 번째 인스턴스" 출력
print(s1 is s2) # True 출력, 두 인스턴스는 동일함을 확인
요약⚑
- 싱글톤 패턴은 클래스의 인스턴스가 하나만 생성되도록 보장하면서도, 해당 인스턴스에 전역적으로 접근할 수 있는 방법을 제공함.
- 이 패턴은 주로 애플리케이션에서 유일한 인스턴스가 필요할 때, 또는 전역 접근이 필요한 객체가 있을 때 유용함. 하지만 멀티스레딩 환경에서 주의 깊게 구현하지 않으면 문제가 발생할 수 있음.