Menu
Coddy logo textTech

by를 사용한 위임

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

위임은 객체가 작업을 상속하는 대신 다른 객체에 넘기는 것을 의미합니다. Kotlin은 이를 직접 지원합니다: class Polite(private val inner: Greeter) : Greeter by inner는 모든 호출을 inner로 전달하여 Greeter를 구현하며, 클래스는 변경하는 부분만 재정의합니다:

interface Greeter {
    fun hello(name: String): String
    fun bye(name: String): String
}
class English : Greeter {
    override fun hello(name: String) = "Hello, $name"
    override fun bye(name: String) = "Bye, $name"
}
class Polite(private val inner: Greeter) : Greeter by inner {
    override fun hello(name: String) = inner.hello(name) + ", pleased to meet you"
}

main 내부에서:

val g = Polite(English())
println(g.hello("Ada"))
println(g.bye("Ada"))

출력:

Hello, Ada, pleased to meet you
Bye, Ada

위임은 상속의 대안입니다. 구현이 final인 인터페이스와 함께 작동하며, 위임 대상은 프로그램 실행 중에 선택되는 어떤 객체든 될 수 있습니다. 이 집합은 모든 add 호출을 세고 나머지는 일반 MutableSet으로 전달합니다:

class CountingSet<T>(private val inner: MutableSet<T> = mutableSetOf()) : MutableSet<T> by inner {
    var attempts = 0
        private set

    override fun add(element: T): Boolean {
        attempts++
        return inner.add(element)
    }
}

main 내부:

val tags = CountingSet<String>()
tags.add("kotlin")
tags.add("jvm")
tags.add("kotlin")
println("${tags.size} tags after ${tags.attempts} adds, contains jvm: ${"jvm" in tags}")

출력:

2 tags after 3 adds, contains jvm: true

위임된 프로퍼티는 다시 by를 사용하여 프로퍼티의 getter와 setter를 다른 객체에 맡깁니다. 2장의 lazy가 이러한 위임자 중 하나이며, 맵도 또 다른 위임자가 될 수 있습니다. 각 프로퍼티는 자신의 이름으로 된 항목을 읽습니다:

class User(values: Map<String, Any?>) {
    val name: String by values
    val age: Int by values
}

main 내부에서:

val u = User(mapOf("name" to "Ada", "age" to 36))
println("${u.name} is ${u.age}")

출력:

Ada is 36

직접 프로퍼티 위임자를 작성할 수 있습니다. operator fun getValue를 포함하는 클래스이며, var에는 setValue도 추가합니다. Kotlin은 프로퍼티를 읽고 쓸 때마다 이들을 호출합니다. 다음 위임자는 할당된 모든 텍스트에서 공백을 제거하고 변경 횟수를 셉니다:

import kotlin.reflect.KProperty

class Trimmed {
    private var value = ""
    var changes = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        value = newValue.trim()
        changes++
    }
}

class Form {
    val nameField = Trimmed()
    var name: String by nameField
}

main 내부에서:

val form = Form()
form.name = "  Ada  "
form.name = "Ada L. "
println("[${form.name}] after ${form.nameField.changes} changes")

출력:

[Ada L.] after 2 changes
challenge icon

챌린지

쉬움

인터페이스 Storage(put, get 및 keys 포함)와 그 구현인 MapStorage가 제공됩니다. 클래스 위임을 사용하여 각각 put만 재정의하는 두 개의 데코레이터를 작성하세요. LoggingStorage(inner)는 읽기 전용 log 목록에 put a=1을 기록하고 호출을 전달하며, LimitedStorage(inner, max)는 max개의 키가 존재할 때 새 키를 추가하려는 요청을 거부합니다(false 반환). 기존 키의 업데이트는 계속 허용됩니다. 그 외의 모든 것은 by를 사용하여 전달합니다.

제공된 코드는 MapStorage를 LoggingStorage로 감싸며, 첫 번째 줄이 양수 제한일 경우 다시 LimitedStorage로 감쌉니다. put a 1, get a 및 keys 명령을 실행하여 ok, refused, 값 또는 missing, 그리고 키 목록을 출력하고, 마지막으로 log: put a=1, ...을 출력합니다.

코드는 LoggingStorage.kt, LimitedStorage.kt, Storage.kt 및 MapStorage.kt에 작성하세요. Main.kt에는 제공된 입출력 코드가 포함되어 있으며 수정할 수 없습니다.

직접 해보기

fun main() {
    // 제공된 입력/출력 코드: 그대로 유지하세요
    val input = generateSequence(::readLine).toList()
    val base = MapStorage()
    val logging = LoggingStorage(base)
    val limit = input[0].toInt()
    val storage: Storage = if (limit > 0) LimitedStorage(logging, limit) else logging
    for (cmd in input.drop(1)) {
        val p = cmd.split(" ")
        when (p[0]) {
            "put" -> println(if (storage.put(p[1], p[2])) "ok" else "refused")
            "get" -> println(storage.get(p[1]) ?: "missing")
            else -> println(storage.keys())
        }
    }
    println("log: " + logging.log.joinToString(", ").ifEmpty { "empty" })
}
quiz icon실력 점검

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

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

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