CS/디자인 패턴

싱글턴(Singleton)패턴

glorypang 2025. 6. 23. 09:53
728x90
반응형
SMALL

1. 의도

우리가 프로그램을 만들다 보면, 하나만 존재해야 하는 객체가 필요할 때가 있어요.

예를 들어:

  • 설정 관리 객체 (Configuration Manager)
  • 로깅 시스템 (Logger)
  • DB 연결 객체 (Database Connection Pool)

이런 객체는 여러 개 만들면 오히려 문제를 일으키죠. 설정이 중복되거나 로그가 꼬일 수도 있고, DB 커넥션이 과하게 열릴 수도 있어요.

이런 경우, 객체는 하나만 존재해야 하고, 모든 곳에서 그 하나를 함께 써야 합니다.

바로 이럴 때 등장하는 게 싱글턴 패턴이에요.

프로그램 안에서 객체를 단 하나만 만들고,

필요할 때 언제든 그 객체를 가져다 쓸 수 있게 해주는 패턴이죠.


2. 문제

싱글턴 패턴은 사실 두 가지 문제를 동시에 해결하려는 방식입니다.
이 두 문제는 각각 독립적으로도 중요한데, 싱글턴은 이를 한 클래스 안에서 함께 처리하면서 단일 책임 원칙(SRP)을 위반할 소지가 있습니다.

1. 인스턴스를 오직 하나만 만들고, 공유 자원을 통제하고 싶다

  • 어떤 클래스는 여러 개 만들면 문제가 생깁니다.
  • 예: DB 연결, 파일 핸들러, 설정 객체 등은 여러 개 생기면 충돌, 낭비, 불일치가 발생할 수 있어요.
  • 그래서 객체를 만들 때마다 새 인스턴스를 주는 게 아니라, 이미 있는 걸 재사용해야 하는 상황이 생깁니다.
  • 하지만 일반 생성자는 호출할 때마다 새 객체를 만들어버리니, 생성자만으로는 이 요구를 만족시킬 수 없습니다.

2. 프로그램 어디서든 객체에 접근할 수 있어야 한다

  • 전역 변수처럼 편하게 접근하고 싶은 경우도 있어요.
  • 그러나 전역 변수는 누구나 값을 바꿀 수 있어서 위험합니다. 잘못 덮어쓰면 프로그램 전체에 영향을 줄 수 있죠.
  • 싱글턴은 이런 접근성을 제공하면서도, 인스턴스를 외부에서 마음대로 바꾸지 못하게 보호합니다.

정리하자면, 싱글턴은

객체를 하나만 만들고 재사용하고 싶은 요구와
그 객체를 전역처럼 어디서든 쓰고 싶은 요구

이 두 가지를 동시에 만족시킵니다.

 

하지만 이 두 역할이 하나의 클래스에 몰려 있다는 점에서,

잘못 쓰면 단일 책임 원칙을 어기고, 유지보수와 테스트가 어려워질 수 있습니다.


2. 해결책

싱글턴 패턴은 객체가 오직 하나만 만들어지도록 강제하면서도,

프로그램 어디에서든 그 객체에 접근할 수 있게 하기 위해 특정한 구조적 방법을 사용합니다.

핵심은 두 가지입니다:

1. 생성자를 막아서 직접 생성하지 못하게 한다

  • 클래스의 생성자를 `private`(또는 `protected`)으로 선언합니다.
  • 이렇게 하면 다른 클래스에서 `new` 키워드로 객체를 만들 수 없게 됩니다.
  • 즉, 개발자가 실수로 객체를 여러 개 만들 수 없도록 차단하는 거죠.

2. 정적 메서드를 통해 단 하나의 인스턴스를 관리한다

  • 클래스 내부에 `static` 필드로 인스턴스를 하나 저장해 둡니다.
  • 외부에서는 오직 `getInstance()` 같은 정적 메서드를 통해서만 이 객체에 접근할 수 있습니다.
  • 이 메서드는 처음 호출될 때만 객체를 생성하고, 그 이후엔 항상 같은 객체를 반환합니다.
public class Singleton {
    private static Singleton instance;

    private Singleton() { }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

핵심은 객체 생성을 통제하고, 접근은 열어두는 구조를 만드는 것

생성은 내부에서 한 번만, 접근은 어디서든 가능하게!

이런 방식 덕분에 코드 어디에서든 싱글턴 인스턴스를 사용할 수 있고,

동시에 객체가 중복 생성되는 문제도 피할 수 있습니다.

구조 요약

  • 싱글턴 클래스
    • `getInstance()` 정적 메서드를 통해 접근
    • 내부적으로 하나의 인스턴스를 관리
  • 비공개 생성자
    • 외부에서 직접 객체 생성을 막음
  • 정적 필드
    • 객체를 저장하고 재사용함

3. 구조

싱글턴 예시: `Database` 클래스

클래스: Database
목적: 앱 전역에서 하나의 DB 연결 인스턴스를 공유하도록 한다

1. 싱글턴 인스턴스를 저장할 정적 필드

private static instance: Database
  • 클래스 내부에 `instance`라는 정적 필드를 선언
  • 최초 한 번만 생성되어, 이후 재사용됨

2. 외부에서 직접 생성 못 하게 생성자를 `private`으로 선언

private constructor Database()
  • `new Database()`를 외부에서 못 쓰게 막음
  • 객체 생성은 클래스 내부에서만 가능함

3. 인스턴스를 생성하거나 반환하는 `getInstance()` 메서드

public static method getInstance()
    if (instance == null)
        acquireThreadLock()
            if (instance == null)  // 두 번째 체크 (double-checked locking)
                instance = new Database()
    return instance
  • 이 메서드는 인스턴스가 없으면 새로 생성하고,
  • 있으면 기존 인스턴스를 반환합니다.
  • 멀티스레드 환경에서도 중복 생성되지 않도록 `lock`과 `double-check` 사용

4. 실제 비즈니스 로직을 수행하는 메서드

public method query(sql)
// 예: 쿼리 실행, 캐싱, 로깅 등
  • 이 메서드를 통해 모든 DB 작업이 이루어짐
  • 하나의 인스턴스를 공유하므로, DB 연결/캐시/스로틀링을 중앙 집중적으로 관리 가능

5. 클라이언트 코드 사용 예

method main()
    db1 = Database.getInstance()
    db1.query("SELECT * FROM users")

    db2 = Database.getInstance()
    db2.query("SELECT * FROM orders")

    // db1과 db2는 동일한 인스턴스를 참조

4. 이 패턴은 언제 쓰면 좋을까?

1. 프로그램 전체에서 인스턴스를 하나만 공유해야 할 때

  • 예: 데이터베이스 연결 객체, 설정 관리 객체, 로깅 시스템
  • 이런 객체는 여러 개 만들면 충돌하거나 자원을 낭비할 수 있습니다.
  • 싱글턴은 이런 객체를 딱 하나만 만들고, 전체에서 함께 사용할 수 있게 해줍니다.

2. 전역 변수처럼 쓰고 싶지만, 더 안전하게 통제하고 싶을 때

  • 전역 변수는 접근은 쉽지만 제어가 어렵고 위험성이 큽니다.
  • 싱글턴은 같은 전역 접근성을 제공하면서도,
    • 외부에서 인스턴스를 덮어쓰지 못하도록 막고
    • 생성 시점도 통제할 수 있어 더 안정적입니다.

3. 리소스 절약 + 일관성 유지가 중요한 경우

  • 객체를 여러 번 만들 필요 없이 재사용하므로 메모리 절약
  • 같은 객체를 쓰기 때문에 상태가 일관되게 유지

5. 장점

  • 인스턴스를 하나로 제한할 수 있다
    • 애초에 한 개만 있어야 하는 객체(DB 연결, 설정 등)에 적합합니다.
  • 전역 접근 지점을 제공한다
    • `getInstance()`를 통해 어디서든 동일한 객체에 접근할 수 있습니다.
  • 요청 시 생성(Lazy Initialization)
    • 처음 필요할 때까지 객체 생성을 미루기 때문에 불필요한 자원 낭비를 줄일 수 있습니다.

6. 단점

  • 단일 책임 원칙(SRP) 위반 가능성
    • 싱글턴은 인스턴스 생성 관리전역 접근 제공이라는 두 역할을 맡기 때문에, 책임이 두 개로 분산될 수 있습니다.
  • 테스트가 어렵다
    • 대부분의 싱글턴 클래스는 정적 메서드와 비공개 생성자를 사용합니다.
    • 이는 Mocking이나 Dependency Injection이 어려워져 테스트 유연성을 해칩니다.
  • 멀티스레드 환경에서 위험
    • 동기화 처리를 하지 않으면 여러 개의 인스턴스가 동시에 생성될 수 있는 위험이 존재합니다.
  • 강한 결합도(Coupling)
    • 싱글턴 객체를 직접 참조하는 코드가 많아질수록,
    • 해당 클래스에 대한 의존성이 높아지고 유지보수가 어려워집니다.

 

참고

https://refactoring.guru/ko/design-patterns/singleton

728x90
반응형
LIST