Delegazione con by
Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 46 di 57.
La delega significa che un oggetto affida il lavoro a un altro oggetto invece di ereditarlo. Kotlin la supporta direttamente: class Polite(private val inner: Greeter) : Greeter by inner implementa Greeter inoltrando ogni chiamata a inner, e la classe sovrascrive solo ciò che modifica:
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"
}All'interno di main:
val g = Polite(English())
println(g.hello("Ada"))
println(g.bye("Ada"))Output:
Hello, Ada, pleased to meet you
Bye, AdaLa delega è un’alternativa all’ereditarietà. Funziona con interfacce le cui implementazioni sono finali e il delegato può essere qualsiasi oggetto scelto durante l’esecuzione del programma. Questo insieme conta ogni add e inoltra tutto il resto a un normale 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)
}
}All’interno di 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}")Output:
2 tags after 3 adds, contains jvm: trueLe proprietà delegate affidano il getter e il setter di una proprietà a un altro oggetto, sempre con by. lazy del capitolo 2 è un esempio di delega, e anche una mappa può esserlo: ogni proprietà legge la voce con il proprio nome:
class User(values: Map<String, Any?>) {
val name: String by values
val age: Int by values
}Dentro main:
val u = User(mapOf("name" to "Ada", "age" to 36))
println("${u.name} is ${u.age}")Output:
Ada is 36Puoi scrivere un delegato di proprietà personalizzato: una classe con un operator fun getValue e, per una var, anche setValue. Kotlin li richiama a ogni lettura e scrittura della proprietà. Questo delegato rimuove gli spazi superflui da ogni testo assegnato e conta le modifiche:
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
}All'interno di main:
val form = Form()
form.name = " Ada "
form.name = "Ada L. "
println("[${form.name}] after ${form.nameField.changes} changes")Output:
[Ada L.] after 2 changesSfida
FacileL'interfaccia Storage (con put, get e keys) e la sua implementazione MapStorage sono fornite. Scrivi due decoratori con delega di classe, ciascuno dei quali sovrascrive solo put: LoggingStorage(inner) registra put a=1 nella sua lista log di sola lettura e inoltra la chiamata, mentre LimitedStorage(inner, max) rifiuta (restituisce false) di aggiungere una nuova chiave quando esistono già max chiavi, consentendo comunque gli aggiornamenti delle chiavi esistenti. Tutto il resto viene inoltrato con by.
Il codice fornito racchiude un MapStorage in un LoggingStorage e, quando la prima riga è un limite positivo, lo racchiude in un LimitedStorage. Esegue i comandi put a 1, get a e keys, stampando ok, refused, il valore oppure missing, e l'elenco delle chiavi, infine log: put a=1, ....
Il tuo codice va inserito in LoggingStorage.kt, LimitedStorage.kt, Storage.kt e MapStorage.kt. Main.kt contiene il codice di input/output fornito e non può essere modificato.
Provalo tu
fun main() {
// Codice di input/output fornito: lascialo così com'è
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" })
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Classi e oggetti
Lavorare con i fileDefinire le classiCostruttori primariBlocchi init e convalidaCostruttori secondariRiepilogo - Tessera della biblioteca4Ereditarietà
Classi aperte e sottoclassiRidefinizione dei membrisuper e costruttoriClassi astratteRiepilogo - Elenco del personale2Proprietà e incapsulamento
Accessor personalizzatiModificatori di visibilitàSetter privatilazy e lateinitRipasso - Conto bancario5Interfacce
Dichiarare le interfacceMetodi predefinitiInterfacce multipleLe interfacce come tipiRiepilogo - Metodi di pagamento8Tipi generici
Funzioni genericheClassi genericheVincoli sui tipiVarianza con in e outRiepilogo - Coda con priorità11Progetto: Gestione della biblioteca
Libri e sociPrendere in prestito i libriEsercitati da solo: Playground Kotlin