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, AdaDelegowanie 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: trueWł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 36Moż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 changesWyzwanie
ŁatwyDostarczono 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" })
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Klasy i obiekty
Praca z plikamiDefiniowanie klasKonstruktory główneBloki init i walidacjaKonstruktory dodatkowePodsumowanie — karta biblioteczna4Dziedziczenie
Klasy otwarte i podklasyNadpisywanie składowychsuper i konstruktoryKlasy abstrakcyjnePodsumowanie – katalog pracowników7Klasy specjalne
Klasy danychKlasy wyliczenioweHierarchie zapieczętowaneequals, hashCode, toStringPodsumowanie — śledzenie zamówień10Wzorce projektowe, część 2
Delegowanie za pomocą byWzorzec dekoratoraWzorzec budowniczegoWzorzec stanu2Właściwości i hermetyzacja
Własne akcesoryModyfikatory widocznościPrywatne setterylazy i lateinitPowtórka – konto bankowe5Interfejsy
Deklarowanie interfejsówMetody domyślneWiele interfejsówInterfejsy jako typyPowtórzenie – metody płatności8Typy generyczne
Funkcje generyczneKlasy generyczneOgraniczenia typówKowariancja i kontrawariancja: in i outPodsumowanie — kolejka priorytetowa11Projekt: Zarządzanie biblioteką
Książki i czytelnicyWypożyczanie książekPoćwicz samodzielnie: Edytor online Kotlin