싱글톤 패턴
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 86번째.
Singleton 패턴은 프로그램 전체에서 한 타입의 인스턴스가 하나만 존재하도록 보장하고, 해당 인스턴스에 접근할 수 있는 전역 지점을 제공합니다. 이는 configuration 관리자, 데이터베이스 연결 또는 logger와 같은 공유 리소스에 유용합니다.
Go에서는 스레드로부터 안전한 초기화를 보장하기 위해 패키지 수준 변수와 sync.Once를 함께 사용하여 Singleton을 구현합니다:
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 instance만 존재하도록 보장하는 Logger 싱글턴을 만들어 봅시다! 이는 Singleton 패턴의 전형적인 사용 사례입니다. 프로그램의 모든 부분에서 동일한 logger 구성과 상태를 공유하도록 만들 것입니다.
코드를 두 파일로 구성합니다:
logger.go: 스레드 안전한 Singleton logger를 Implement합니다.두 fields를 가진
Loggerstruct를 만듭니다. 로그 message의 접두사를 위한Prefix(string)와, logged된 message의 수를 추적하기 위한MessageCount(int)입니다.sync.Once를 사용하는 package-level 변수를 이용해 instance가 한 번만 생성되도록 ensure합니다. logger를"[LOG]"이라는 default prefix로 초기화하고 Singleton instance를 반환하는GetLogger()function을 Implement합니다.Logger에 다음 methods를 추가합니다:
SetPrefix(prefix string)- logger의 prefix를 changes합니다Log(message string) string- message count를 increments하고 formatted string을 반환합니다:[prefix] #[count]: [message]GetCount() int- logged된 message의 전체 number를 반환합니다
main.go:GetLogger()를 여러 번 호출해도 동일한 instance가 반환된다는 것을 보여 줍니다.새 prefix 값을 읽은 다음, messages의 count와 로그로 기록할 각 message를 읽습니다.
logger instance를 가져오고 custom prefix를 설정한 다음, 각 message를 Log하고 result를 출력합니다. 모든 messages를 Log한 후 logger instance를 다시 가져와(동일한 instance임을 증명하기 위해) 전체 message count를 출력합니다.
다음 inputs가 제공됩니다:
- Line 1: 설정할 custom prefix
- Line 2: messages의 number
- Following lines: Log할 각 message
예를 들어, 다음과 같은 경우:
[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()를 몇 번 호출하더라도 항상 동일한 instance를 얻게 되며, 그 instance가 공유 상태를 가진다는 점입니다. 존재하는 Logger가 하나뿐이므로 message count는 유지됩니다!
직접 해보기
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: 로거 인스턴스를 다시 가져오기 (동일한 인스턴스임을 증명하기 위해)
// and print the total message count in format: "Total messages logged: X"
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서직접 연습해 보세요: 온라인 Go 컴파일러