Menu
Coddy logo textTech

싱글턴 패턴

Coddy Kotlin 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 42번째.

디자인 패턴은 반복해서 발생하는 디자인 문제에 대한 검증된 해결책입니다. 싱글턴 패턴은 클래스에 전체 프로그램이 접근할 수 있는 인스턴스가 정확히 하나만 있도록 보장합니다. Kotlin에서 object 선언은 싱글턴입니다. 처음 사용될 때 한 번 생성되며, 여러 스레드가 동시에 시작될 때도 안전합니다:

object Settings {
    var theme = "light"
    var fontSize = 14
}

fun openEditor() = "editor in ${Settings.theme} theme"

main 내부에서:

Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)

출력:

editor in dark theme
14

싱글턴은 프로그램의 여러 부분이 공유하는 상태를 보유합니다. 해당 상태를 비공개로 유지하고 메서드를 통해서만 변경하여 모든 변경이 동일한 규칙을 따르도록 하세요:

object Metrics {
    private val hits = mutableMapOf<String, Int>()

    fun hit(page: String) { hits[page] = (hits[page] ?: 0) + 1 }
    fun report() = hits.entries.joinToString { "${it.key}=${it.value}" }
}

fun home() = Metrics.hit("home")
fun about() = Metrics.hit("about")

main 내부에서:

home(); about(); home()
println(Metrics.report())

출력:

home=2, about=1

단일 인스턴스에 설정 데이터가 필요할 때는 private constructor가 있는 클래스가 첫 번째 요청 시 companion object에서 인스턴스를 생성하고 이후에는 동일한 인스턴스를 반환할 수 있습니다:

class Database private constructor(val url: String) {
    companion object {
        private var instance: Database? = null

        fun connect(url: String): Database =
            instance ?: Database(url).also { instance = it }
    }
}

main 내부:

val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)

출력:

true
db://main

싱글턴은 신중하게 사용하세요. 싱글턴은 전역 상태이므로 어떤 코드든 이를 변경할 수 있고, 누가 이에 의존하는지 알기 어렵게 만들며, 테스트를 새로운 복사본에서 시작할 수 없게 합니다. 구성이나 로거처럼 정말로 프로그램마다 하나만 존재해야 하는 경우에 적합합니다. 프로그램의 일부만 객체를 필요로 한다면 인터페이스 타입의 매개변수로 전달하세요. 그러면 테스트에서 대신 가짜 객체를 전달할 수 있습니다:

interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }

class Session(private val clock: Clock) {      // 의존성이 보입니다
    val started = clock.now()
}
val session = Session(SystemClock)             // 테스트가 가짜 Clock을 전달합니다
challenge icon

챌린지

쉬움

싱글턴 Logger를 완성하세요. 변경 가능한 minLevel(순서대로 debug, info, warn 또는 error)이 있습니다. log(level, message)는 레벨이 minLevel 이상이면 [WARN] disk low를 저장하고, 그렇지 않으면 메시지를 dropped에 집계합니다. dropped는 코드 외부에서 읽을 수 있지만 변경할 수는 없습니다. entries는 저장된 줄의 읽기 전용 목록이며, count(level)은 해당 레벨을 가진 저장된 줄의 개수를 반환합니다.

Functions.kt의 도우미 함수는 제공됩니다. 이 함수들은 다른 파일에서 동일한 Logger에 접근합니다. 제공된 코드는 최소 레벨과 level,message 형식의 줄을 읽은 다음 모든 항목과 errors: 1, dropped: 2를 출력합니다.

코드는 Logger.kt와 Functions.kt에 작성하세요. Main.kt에는 제공된 입력/출력 코드가 들어 있으며 편집할 수 없습니다.

직접 해보기

fun main() {
    // 제공된 입출력 코드: 있는 그대로 유지하세요
    val input = generateSequence(::readLine).toList()
    Logger.minLevel = input[0]
    for (line in input.drop(1)) {
        val (level, message) = line.split(",", limit = 2)
        when (level) {
            "debug" -> debug(message)
            "info" -> info(message)
            "warn" -> warn(message)
            else -> error(message)
        }
    }
    for (e in Logger.entries) println(e)
    println("errors: ${Logger.count("error")}, dropped: ${Logger.dropped}")
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: Kotlin 플레이그라운드