싱글톤 패턴
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 86번째.
싱글톤 패턴(Singleton pattern)은 프로그램 전체에서 특정 타입의 인스턴스가 오직 하나만 존재하도록 보장하며, 해당 인스턴스에 대한 전역적인 접근 지점을 제공합니다. 이는 설정 관리자, 데이터베이스 연결 또는 로거와 같은 공유 리소스에 유용합니다.
Go에서 싱글톤(Singleton)은 패키지 수준 변수와 sync.Once를 결합하여 구현하며, 이를 통해 스레드 안전한(thread-safe) 초기화를 보장합니다:
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}sync.Once는 여러 고루틴이 동시에 GetInstance()를 호출하더라도 초기화 함수가 정확히 한 번만 실행되도록 보장합니다. 이후의 모든 호출은 초기화 코드를 다시 실행하지 않고 동일한 인스턴스를 반환합니다.
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1과 cfg2는 동일한 인스턴스를 가리킵니다Singleton은 신중하게 사용하세요. 편리하긴 하지만, 테스트를 어렵게 만들고 의존성을 숨길 수 있는 전역 상태를 도입합니다. 여러분의 사용 사례에 의존성 주입(dependency injection)이 더 나은 대안이 될 수 있는지 고려해 보세요.
챌린지
쉬움애플리케이션 전체에서 단 하나의 로거 인스턴스만 존재하도록 보장하는 Logger 싱글톤을 만들어 봅시다! 이것은 싱글톤 패턴의 전형적인 사용 사례입니다. 프로그램의 모든 부분에서 동일한 로거 설정과 상태를 공유하기를 원할 때 사용합니다.
코드는 두 개의 파일로 구성됩니다:
logger.go: 스레드 세이프(thread-safe)한 싱글톤 로거를 구현합니다.로그 메시지 접두사를 위한
Prefix(string) 필드와 기록된 메시지 수를 추적하는MessageCount(int) 필드를 가진Logger구조체를 생성하세요.sync.Once와 함께 패키지 수준 변수를 사용하여 인스턴스가 단 하나만 생성되도록 보장하세요. 로거를 기본 접두사"[LOG]"로 초기화하고 싱글톤 인스턴스를 반환하는GetLogger()함수를 구현하세요.Logger에 다음 메서드들을 추가하세요:
SetPrefix(prefix string)- 로거의 접두사를 변경합니다.Log(message string) string- 메시지 카운트를 증가시키고[prefix] #[count]: [message]형식의 문자열을 반환합니다.GetCount() int- 기록된 총 메시지 수를 반환합니다.
main.go:GetLogger()를 여러 번 호출해도 동일한 인스턴스가 반환됨을 증명합니다.새로운 접두사 값을 읽은 다음, 기록할 메시지 수와 각 메시지를 차례로 읽습니다.
로거 인스턴스를 가져와서 사용자 정의 접두사를 설정한 후, 각 메시지를 기록하고 결과를 출력하세요. 모든 메시지를 기록한 후, 로거 인스턴스를 다시 가져와서(동일한 인스턴스임을 증명하기 위해) 총 메시지 수를 출력하세요.
다음 입력이 제공됩니다:
- Line 1: 설정할 사용자 정의 접두사
- Line 2: 메시지 수
- Following lines: 기록할 각 메시지
예를 들어, 다음과 같이 입력되면:
[APP]
3
Server started
User connected
Request processed출력은 다음과 같아야 합니다:
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3그리고 다음과 같이 입력되면:
[DEBUG]
2
Initializing cache
Cache ready출력은 다음과 같아야 합니다:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2그리고 다음과 같이 입력되면:
[ERROR]
1
Connection failed출력은 다음과 같아야 합니다:
[ERROR] #1: Connection failed
Total messages logged: 1핵심은 GetLogger()를 몇 번 호출하든 상관없이 항상 공유된 상태를 가진 동일한 인스턴스를 얻는다는 것입니다. 단 하나의 Logger만 존재하기 때문에 메시지 카운트가 유지됩니다!
치트 시트
싱글톤 패턴(Singleton pattern)은 프로그램 전체에서 특정 타입의 인스턴스가 단 하나만 존재하도록 보장하며, 해당 인스턴스에 대한 전역적인 접근 지점을 제공합니다. 이는 설정 관리자, 데이터베이스 연결 또는 로거와 같은 공유 리소스에 유용합니다.
스레드 세이프(thread-safe)한 초기화를 위해 패키지 레벨 변수와 sync.Once를 결합하여 싱글톤을 구현합니다:
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}sync.Once는 여러 고루틴이 동시에 GetInstance()를 호출하더라도 초기화 함수가 정확히 한 번만 실행되도록 보장합니다. 이후의 모든 호출은 동일한 인스턴스를 반환합니다:
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 and cfg2 point to the same instance싱글톤은 테스트를 어렵게 만들고 의존성을 숨길 수 있는 전역 상태를 도입하므로 신중하게 사용해야 합니다. 대안으로 의존성 주입(dependency injection)을 고려해 보세요.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 사용자 정의 접두사 읽기
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// 메시지 수 읽기
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// 각 메시지 읽기
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: GetLogger()를 사용하여 로거 인스턴스 가져오기
// TODO: SetPrefix()를 사용하여 사용자 정의 접두사 설정하기
// TODO: 각 메시지를 로깅하고 결과 출력하기
// TODO: 로거 인스턴스를 다시 가져오기 (동일한 인스턴스임을 증명하기 위해)
// 그리고 "Total messages logged: X" 형식으로 전체 메시지 수 출력하기
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서