Menu
Coddy logo textTech

Delegowanie za pomocą by

Część sekcji Programowanie obiektowe ścieżki Kotlin w Coddy. Lekcja 46 z 57.

Delegowanie oznacza, że obiekt przekazuje pracę innemu obiektowi zamiast ją dziedziczyć. Kotlin obsługuje je bezpośrednio: class Polite(private val inner: Greeter) : Greeter by inner implementuje Greeter, przekazując każde wywołanie do inner, a klasa nadpisuje tylko to, co zmienia:

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"
}

Wewnątrz main:

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

Wynik:

Hello, Ada, pleased to meet you
Bye, Ada

Delegowanie jest alternatywą dla dziedziczenia. Działa z interfejsami, których implementacje są finalne, a delegatem może być dowolny obiekt wybrany podczas działania programu. Ten zbiór zlicza każde add i przekazuje wszystko inne do zwykłego 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)
    }
}

Wewnątrz 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}")

Wynik:

2 tags after 3 adds, contains jvm: true

Właściwości delegowane przekazują getter i setter właściwości innemu obiektowi, ponownie za pomocą by. lazy z rozdziału 2 jest jednym z takich delegatów, a mapa może być innym: każda właściwość odczytuje wpis o swojej nazwie:

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

Wewnątrz main:

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

Wynik:

Ada is 36

Możesz napisać własny delegat właściwości: klasę z operator fun getValue, a także setValue dla var. Kotlin wywołuje je przy każdym odczycie i zapisie właściwości. Ten delegat przycina każdy przypisany tekst i zlicza zmiany:

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
}

Wewnątrz main:

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

Wynik:

[Ada L.] after 2 changes
challenge icon

Wyzwanie

Łatwy

Dostarczono interfejs Storage (z metodami put, get i keys) oraz jego implementację MapStorage. Napisz dwa dekoratory z delegacją klas, z których każdy nadpisuje tylko put: LoggingStorage(inner) zapisuje put a=1 na swojej liście log tylko do odczytu i przekazuje wywołanie dalej, a LimitedStorage(inner, max) odmawia (zwraca false) dodania nowego klucza, gdy istnieje już max kluczy, ale nadal zezwala na aktualizowanie istniejących kluczy. Wszystkie pozostałe metody są przekazywane dalej za pomocą by.

Dostarczony kod opakowuje MapStorage w LoggingStorage, a gdy pierwszy wiersz zawiera dodatni limit, opakowuje go także w LimitedStorage. Wykonuje polecenia put a 1, get a i keys, wypisując ok, refused, wartość lub missing oraz listę kluczy, a na końcu log: put a=1, ....

Umieść swój kod w plikach LoggingStorage.kt, LimitedStorage.kt, Storage.kt i MapStorage.kt. Main.kt zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

fun main() {
    // Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
    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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Edytor online Kotlin