Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 341 additions & 0 deletions jina/05.싱글톤_패턴.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
# 싱글톤 패턴

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

## 싱글톤 패턴의 구조

```typescript
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. **상태 공유**
- 여러 곳에서 같은 상태를 공유해야 할 때 유용
- 설정, 로거, 캐시 등에 적합

## 실제 사용 예제

### 초콜릿 보일러 시스템

```typescript
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;
}
}
```

### 데이터베이스 연결 관리

```typescript
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. 클래스 기반 싱글톤

```typescript
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)를 사용한 싱글톤

```typescript
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. 객체 리터럴 싱글톤

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

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

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

### 4. ES6 모듈 싱글톤

```typescript
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)

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

private constructor() {}

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

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

#### 2. DCL(Double-Checked Locking) 기법

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

## 주요 사용 사례

### 설정 관리

```typescript
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. **전역 상태 관리**
- 전역 상태는 디버깅을 어렵게 만들 수 있음
- 필요한 경우에만 제한적으로 사용