Skip to content
2글자 이상 입력하세요
creational으로

Singleton

8 min 읽기
creational-patternssingletonglobal-accessdesign-patterns

Singleton

Singleton은 클래스가 오직 하나의 인스턴스만 갖도록 보장하면서 이 인스턴스에 대한 전역 접근점을 제공하는 생성 디자인 패턴입니다.

Singleton pattern

Intent

Singleton 패턴은 두 가지 문제를 동시에 해결합니다:

  1. 클래스에 인스턴스가 하나만 있도록 보장: 데이터베이스나 파일 같은 공유 리소스에 대한 접근을 제어할 때 유용합니다.
  2. 해당 인스턴스에 대한 전역 접근점 제공: 전역 변수처럼 어디서든 접근할 수 있지만, 덮어쓰기로부터 보호됩니다.

Problem

Singleton 패턴은 Single Responsibility Principle을 위반하여 두 가지 문제를 동시에 해결합니다:

단일 인스턴스 보장

일반 생성자는 항상 새로운 객체를 반환하므로, 생성자 호출만으로는 단일 인스턴스를 보장할 수 없습니다. 데이터베이스 연결이나 파일 시스템 접근 같은 공유 리소스를 제어하려면 단일 인스턴스가 필요합니다.

전역 접근점 제공

전역 변수는 편리하지만 위험합니다. 어떤 코드든 변수의 내용을 덮어쓸 수 있어 앱이 충돌할 수 있습니다. Singleton은 이 문제를 해결합니다.

Solution

모든 Singleton 구현에는 두 가지 공통 단계가 있습니다:

  1. 기본 생성자를 private으로 설정: 다른 객체가 new 연산자를 사용하지 못하도록 합니다.
  2. 정적 생성 메서드 제공: 생성자 역할을 하며, 내부적으로 private 생성자를 호출하여 객체를 생성하고 정적 필드에 저장합니다. 이후 모든 호출은 캐시된 객체를 반환합니다.

Real-World Analogy

정부 예시

정부가 Singleton 패턴의 좋은 예시입니다:

Structure

Singleton 구조

Singleton 구조 상세

구성 요소역할
Singleton정적 메서드 getInstance()를 선언하여 동일한 인스턴스를 반환. 생성자는 클라이언트 코드로부터 숨겨짐

Pseudocode

데이터베이스 연결을 관리하는 Singleton 예시:

class Database {
private static instance: Database | null = null;
// private 생성자로 외부에서 직접 인스턴스 생성 방지
private constructor() {
// 데이터베이스 초기화 코드
console.log("Database connection initialized");
}
// 인스턴스를 가져오는 정적 메서드
public static getInstance(): Database {
if (Database.instance === null) {
Database.instance = new Database();
}
return Database.instance;
}
public query(sql: string): void {
console.log(`Executing query: ${sql}`);
// 데이터베이스 쿼리 로직
}
}
// 클라이언트 코드
class Application {
main(): void {
const foo = Database.getInstance();
foo.query("SELECT * FROM users");
const bar = Database.getInstance();
bar.query("SELECT * FROM products");
// foo와 bar는 동일한 객체를 참조
console.log(foo === bar); // true
}
}

Thread-Safe 구현

멀티스레드 환경에서는 동시에 여러 스레드가 인스턴스를 생성하려 할 수 있습니다:

class ThreadSafeDatabase {
private static instance: ThreadSafeDatabase | null = null;
private static lock = new Object();
private constructor() {
// 초기화 코드
}
public static getInstance(): ThreadSafeDatabase {
// Double-checked locking
if (ThreadSafeDatabase.instance === null) {
// synchronized(ThreadSafeDatabase.lock) {
if (ThreadSafeDatabase.instance === null) {
ThreadSafeDatabase.instance = new ThreadSafeDatabase();
}
// }
}
return ThreadSafeDatabase.instance;
}
}

Applicability

다음의 경우에 패턴을 사용합니다:

단일 인스턴스가 필요할 때

프로그램의 모든 클라이언트가 공유하는 단일 인스턴스가 필요할 때 사용합니다. 예: 데이터베이스 연결, 로그 관리자 등.

전역 변수를 엄격하게 제어할 때

일반 전역 변수와 달리 Singleton은 인스턴스가 하나만 존재함을 보장합니다. Singleton 클래스만 캐시된 인스턴스를 교체할 수 있습니다.

How to Implement

  1. 정적 필드 추가: Singleton 인스턴스를 저장할 private static 필드를 클래스에 추가합니다.

  2. 정적 생성 메서드 선언: Singleton 인스턴스를 가져오는 public static 메서드를 선언합니다.

  3. 지연 초기화 구현: 메서드 내에서 첫 호출 시 새 객체를 생성하고 정적 필드에 저장합니다. 이후 모든 호출은 이 객체를 반환합니다.

  4. 생성자를 private으로: 클래스 생성자를 private으로 만듭니다. 정적 메서드만 이 생성자를 호출할 수 있습니다.

  5. 클라이언트 코드 수정: 생성자 직접 호출을 정적 생성 메서드 호출로 대체합니다.

Pros and Cons

장점

장점설명
단일 인스턴스 보장클래스에 인스턴스가 하나만 있음을 확신할 수 있습니다
전역 접근점해당 인스턴스에 대한 전역 접근점을 얻습니다
지연 초기화처음 요청될 때만 초기화됩니다

단점

단점설명
SRP 위반두 가지 문제를 동시에 해결합니다
나쁜 설계 은폐컴포넌트가 서로 너무 많이 알고 있음을 숨길 수 있습니다
멀티스레드 환경여러 스레드가 동시에 인스턴스를 생성하지 않도록 처리해야 합니다
단위 테스트 어려움private 생성자와 static 메서드로 인해 모킹이 어렵습니다

Relations with Other Patterns

패턴관계
Facade하나의 facade 객체로 충분하므로 종종 Singleton으로 변환 가능
Flyweight공유 상태를 하나의 flyweight 객체로 줄일 수 있다면 Singleton과 유사해짐
Abstract Factory, Builder, Prototype모두 Singleton으로 구현될 수 있음

출처: refactoring.guru - Singleton


이전 글

Prototype

다음 글

Chain of Responsibility