싱글톤 패턴

Haks.·2025년 6월 18일
0

Study

목록 보기
66/69

싱글톤 패턴(Singleton Pattern): 단 하나의 객체만 허용합니다

소프트웨어 디자인 패턴 중 하나인 싱글톤 패턴(Singleton Pattern)은 특정 클래스의 인스턴스, 즉 객체가 애플리케이션 내에서 단 하나만 생성되는 것을 보장하는 설계 방식입니다. 생성자가 여러 번 호출되더라도 실제로 생성되는 객체는 최초에 생성된 하나이며, 이후의 모든 호출에서는 최초 생성된 객체를 반환합니다.

이는 마치 우리 반에 반장은 단 한 명만 존재하는 것과 같습니다. 누구든 "반장 나와!"라고 외치면 항상 같은 그 한 명의 반장이 나오는 것과 같은 원리입니다.

왜 싱글톤 패턴을 사용할까요?
싱글톤 패턴은 주로 다음과 같은 목적을 위해 사용됩니다.

메모리 낭비 방지: 객체를 생성할 때마다 메모리를 할당받게 됩니다. 만약 여러 곳에서 동일한 역할을 하는 객체가 필요할 때마다 새로운 객체를 생성한다면 불필요한 메모리 소모가 발생합니다. 싱글톤 패턴을 사용하면 최초 한 번만 객체를 생성하고 이후에는 계속해서 재사용하므로 메모리를 절약할 수 있습니다.
데이터 공유 용이: 단 하나뿐인 객체이므로, 애플리케이션의 여러 부분에서 해당 객체의 데이터를 공유하고 상태를 일관성 있게 관리하기 용이합니다. 예를 들어, 애플리케이션 전체의 환경 설정을 관리하는 객체나, 데이터베이스 연결을 관리하는 커넥션 풀(Connection Pool) 같은 경우에 유용하게 사용될 수 있습니다.
전역적인 접근성: 싱글톤 객체는 전역 변수처럼 애플리케이션의 어느 곳에서나 쉽게 접근하여 사용할 수 있습니다. 이를 통해 다른 객체들과의 상호작용이 편리해집니다.

싱글톤 패턴의 핵심 구현 원리

싱글톤 패턴을 구현하기 위해서는 다음의 두 가지 핵심 요소를 지켜야 합니다.

  1. private 생성자: 외부에서 new 키워드를 사용하여 무분별하게 새로운 객체를 생성하는 것을 막기 위해 생성자의 접근 제어자를 private으로 선언합니다.
  2. static 메서드와 static 변수: 유일한 인스턴스를 저장하기 위한 static 변수를 클래스 내부에 선언하고, 이 인스턴스에 접근할 수 있도록 public static 메서드(일반적으로 getInstance()라는 이름을 사용)를 제공합니다. 이 메서드는 인스턴스가 아직 생성되지 않았다면 새로 생성하여 반환하고, 이미 생성되어 있다면 기존의 인스턴스를 반환합니다.

파이썬 구현 예시

class Singleton:
    _instance = None  # 유일한 인스턴스를 저장할 클래스 변수

    def __new__(cls):
        # 인스턴스가 아직 없으면 새로 생성
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            print("싱글톤 객체를 새로 생성했습니다.")
        else:
            print("기존 싱글톤 객체를 반환합니다.")
        return cls._instance

    def __init__(self):
        # __init__은 __new__ 호출 후 매번 호출되므로, 
        # 실제 초기화는 한 번만 수행되도록 주의해야 합니다.
        if not hasattr(self, '_initialized'): # 초기화 플래그를 사용하여 한 번만 초기화되도록
            self.value = "기본값"
            self._initialized = True
            print("싱글톤 객체를 초기화했습니다.")

    def do_something(self):
        print(f"싱글톤 객체에서 작업 수행 중입니다. 현재 값: {self.value}")

# 사용 예시
print("--- 첫 번째 객체 생성 시도 ---")
s1 = Singleton()
s1.value = "첫 번째 설정값"
s1.do_something()

print("\n--- 두 번째 객체 생성 시도 ---")
s2 = Singleton()
s2.do_something()

print("\n--- 세 번째 객체 생성 시도 ---")
s3 = Singleton()
s3.value = "세 번째 설정값 (s1, s2와 동일한 객체)"
s3.do_something()

print("\n--- 객체 동일성 확인 ---")
print(f"s1과 s2는 같은 객체인가요? {s1 is s2}")
print(f"s2와 s3는 같은 객체인가요? {s2 is s3}")
print(f"s1.value: {s1.value}, s2.value: {s2.value}, s3.value: {s3.value}")

코드 설명:

  • _instance = None: 클래스 변수로, 싱글톤 인스턴스를 저장합니다. 초기에는 None으로 설정합니다.
  • __new__(cls):
    - if cls._instance is None:: 만약 _instance가 아직 생성되지 않았다면 (즉, 첫 번째 객체 생성 시도라면)
    - cls._instance = super().__new__(cls): 부모 클래스의 __new__ 메서드를 호출하여 실제 객체를 생성하고 _instance에 저장합니다.
    - else: 이미 _instance가 존재하면 기존의 인스턴스를 반환합니다.
  • __init__(self): __new__는 객체를 생성하고 반환하지만, __init__은 객체가 반환된 후 매번 호출됩니다. 따라서 _initialized와 같은 플래그를 사용하여 실제 초기화 로직은 한 번만 실행되도록 하는 것이 중요합니다.

싱글톤 패턴의 장단점

장점

  • 메모리 효율성: 단 하나의 인스턴스만 사용하므로 메모리 낭비를 줄일 수 있습니다.
  • 데이터 일관성: 전역적으로 하나의 상태를 유지하므로 데이터의 일관성을 보장하기 쉽습니다.
  • 전역 접근: 어디서든 쉽게 접근하여 사용할 수 있어 편리합니다.

단점

  • 의존성 증가: 싱글톤 객체를 사용하는 다른 클래스들과의 결합도(Coupling)가 높아져 유연성이 떨어질 수 있습니다.
  • 테스트의 어려움: 전역 상태를 가지므로 단위 테스트(Unit Test)를 수행하기가 까다롭습니다. 테스트마다 독립적인 환경을 구축하기 어렵기 때문입니다.
  • 단일 책임 원칙 위배 가능성: 하나의 객체가 너무 많은 역할과 데이터를 가지게 될 수 있습니다.
  • 멀티스레드 환경에서의 동시성 문제: 여러 스레드가 동시에 getInstance() 메서드를 호출할 경우, 여러 개의 인스턴스가 생성될 수 있는 문제가 발생할 수 있습니다. 이를 방지하기 위한 동기화 처리가 필요합니다.

0개의 댓글