싱글톤 패턴(헤드퍼스트 디자인패턴 책 스터디)

지리·2026년 3월 22일

싱글톤 패턴

싱글톤 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공하는 패턴

싱글톤 패턴의 구조

class Singleton {
	// 하나뿐인 인스턴스가 저장됨
	private static uniqueInstance: Singleton;

	// private 생성자를 통해 외부에서 인스턴스 생성 불가능
	private constructor() {}

	public static getInstance(): Singleton {
		if (!this.uniqueInstance) {
			this.uniqueInstance = new Singleton();
		}
		return this.uniqueInstance;
	}

	// 기타 메소드
}

핵심 구성 요소

  1. private static 인스턴스 변수

    • 클래스의 하나뿐인 인스턴스를 저장
  2. private 생성자

    • 외부에서 new 키워드로 인스턴스 생성을 방지
  3. public static getInstance() 메서드

    • 전역 접근 지점 제공
    • 인스턴스가 없으면 생성, 있으면 기존 인스턴스 반환

싱글톤 패턴의 장점

  1. 유일한 인스턴스 보장

    • 클래스에 인스턴스가 하나만 존재함을 보장
    • 메모리 낭비 방지
  2. 전역 접근 지점

    • 어디서든 동일한 인스턴스에 접근 가능
    • 데이터 공유 용이
  3. 지연 초기화(Lazy Initialization)

    • 실제로 필요할 때까지 인스턴스 생성을 미룸
    • 리소스 절약
  4. 상태 공유

    • 여러 곳에서 같은 상태를 공유해야 할 때 유용
    • 설정, 로거, 캐시 등에 적합

실제 사용 예제

초콜릿 보일러 시스템

class ChocolateBoiler {
	private static uniqueInstance: ChocolateBoiler;
	private empty: boolean;
	private boiled: boolean;

	private constructor() {
		this.empty = true;
		this.boiled = false;
	}

	public static getInstance(): ChocolateBoiler {
		if (!this.uniqueInstance) {
			this.uniqueInstance = new ChocolateBoiler();
		}
		return this.uniqueInstance;
	}

	public fill(): void {
		if (this.isEmpty()) {
			this.empty = false;
			this.boiled = false;
			// 보일러에 우유와 초콜릿을 혼합한 재료를 넣음
		}
	}

	public drain(): void {
		if (!this.isEmpty() && this.isBoiled()) {
			// 끓인 재료를 다음 단계로 넘김
			this.empty = true;
		}
	}

	public boil(): void {
		if (!this.isEmpty() && !this.isBoiled()) {
			// 재료를 끓임
			this.boiled = true;
		}
	}

	public isEmpty(): boolean {
		return this.empty;
	}

	public isBoiled(): boolean {
		return this.boiled;
	}
}

데이터베이스 연결 관리

class Database {
	private static instance: Database;
	private connection: any;

	private constructor() {
		this.connection = null;
	}

	public static getInstance(): Database {
		if (!Database.instance) {
			Database.instance = new Database();
		}
		return Database.instance;
	}

	connect() {
		if (!this.connection) {
			this.connection = { status: 'connected' };
			console.log('DB 연결됨');
		}
	}
}

// 사용
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true

JavaScript/TypeScript 싱글톤 구현 방법

1. 클래스 기반 싱글톤

class Database {
	private static instance: Database;
	private connection: any;

	private constructor() {
		this.connection = null;
	}

	public static getInstance(): Database {
		if (!Database.instance) {
			Database.instance = new Database();
		}
		return Database.instance;
	}

	connect() {
		if (!this.connection) {
			this.connection = { status: 'connected' };
			console.log('DB 연결됨');
		}
	}
}

장점: 전통적이고 명확함
사용 시기: 일반적인 싱글톤 구현

2. 즉시 실행 함수(IIFE)를 사용한 싱글톤

const UserManager = (() => {
	let instance: any;

	function createInstance() {
		return {
			users: [] as string[],
			addUser(name: string) {
				this.users.push(name);
			},
			getUsers() {
				return this.users;
			},
		};
	}

	return {
		getInstance() {
			if (!instance) {
				instance = createInstance();
			}
			return instance;
		},
	};
})();

// 사용
const userMgr1 = UserManager.getInstance();
const userMgr2 = UserManager.getInstance();
console.log(userMgr1 === userMgr2); // true

장점: 클로저를 활용한 완벽한 캡슐화
사용 시기: private 변수가 필요할 때

3. 객체 리터럴 싱글톤

const AppConfig = {
	apiUrl: 'https://api.example.com',
	timeout: 5000,
	setApiUrl(url: string) {
		this.apiUrl = url;
	},
	getApiUrl() {
		return this.apiUrl;
	},
};

// 객체 리터럴은 그 자체로 싱글톤이며, 추가 인스턴스 생성 불가

장점: 가장 간단하고 직관적
사용 시기: 간단한 설정이나 상수 관리

4. ES6 모듈 싱글톤

class SessionManager {
	private sessionData: Map<string, any> = new Map();

	setSession(key: string, value: any) {
		this.sessionData.set(key, value);
	}

	getSession(key: string) {
		return this.sessionData.get(key);
	}

	clearSession() {
		this.sessionData.clear();
	}
}

// 모듈에서 단 하나의 인스턴스만 생성하고 export
export const sessionManager = new SessionManager();

// 다른 파일에서: import { sessionManager } from './session';
// ES6 모듈은 캐싱되므로 항상 같은 인스턴스를 반환

장점: 가장 자연스럽고 JavaScript다운 방식
사용 시기: 대부분의 실무 상황

             |

멀티스레딩 문제와 해결책

문제 상황

두 스레드에서 getInstance()를 동시에 호출하면 인스턴스가 두 개 이상 생성될 수 있음

JavaScript에서의 상황

JavaScript는 기본적으로 단일 스레드이므로 synchronized 키워드가 필요 없음

하지만 Web Workers를 사용하면 각 워커마다 별도 인스턴스가 생성될 수 있으니 주의

해결 방법

1. 인스턴스를 시작하자마자 생성 (Eager Initialization)

class ChocolateBoiler {
	// 클래스 로딩 시점에 즉시 생성
	private static uniqueInstance = new ChocolateBoiler();

	private constructor() {}

	public static getInstance(): ChocolateBoiler {
		return this.uniqueInstance;
	}
}

장점: 스레드 안전 보장
단점: 사용하지 않아도 인스턴스가 생성됨

2. DCL(Double-Checked Locking) 기법

다른 언어에서 사용하는 기법이지만, JavaScript에서는 단일 스레드 특성상 불필요

주요 사용 사례

설정 관리

export const config = {
	apiUrl: process.env.API_URL,
	apiKey: process.env.API_KEY,
	timeout: 5000,
};

애플리케이션 상태 관리

Redux Store, Vuex Store 등

디자인 원칙

싱글톤 패턴은 다음 디자인 원칙을 따른다:

  • 클래스가 자신의 인스턴스를 관리하도록 한다
  • 전역 접근 지점을 제공하되, 무분별한 전역 변수 사용을 방지한다

주의사항

  1. 테스트의 어려움

    • 전역 상태로 인해 단위 테스트가 어려울 수 있음
    • Mock 객체 주입이 힘듦
  2. 의존성 숨김

    • 코드에서 명시적인 의존성을 숨김
    • 결합도가 높아질 수 있음
  3. 멀티스레드 환경

    • JavaScript는 단일 스레드지만 Web Workers 사용 시 주의
    • 각 워커마다 별도 인스턴스 생성됨
  4. 과도한 사용 지양

    • 모든 것을 싱글톤으로 만들지 말 것
    • 정말 하나의 인스턴스만 필요한 경우에만 사용
  5. 전역 상태 관리

    • 전역 상태는 디버깅을 어렵게 만들 수 있음
    • 필요한 경우에만 제한적으로 사용
profile
공부한것들, 경험한 것들을 기록하려 노력합니다✨

0개의 댓글