Delegation With by
Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 46 of 57.
Delegation means that an object hands work to another object instead of inheriting it. Kotlin supports it directly: class Polite(private val inner: Greeter) : Greeter by inner implements Greeter by forwarding every call to inner, and the class overrides only what it changes:
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"
}Inside main:
val g = Polite(English())
println(g.hello("Ada"))
println(g.bye("Ada"))Output:
Hello, Ada, pleased to meet you
Bye, AdaDelegation is an alternative to inheritance. It works with interfaces whose implementations are final, and the delegate can be any object chosen while the program runs. This set counts every add and forwards everything else to a normal 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)
}
}Inside 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: trueDelegated properties hand a property's getter and setter to another object, again with by. lazy from chapter 2 is one such delegate, and a map can be another: each property reads the entry with its own name:
class User(values: Map<String, Any?>) {
val name: String by values
val age: Int by values
}Inside main:
val u = User(mapOf("name" to "Ada", "age" to 36))
println("${u.name} is ${u.age}")Output:
Ada is 36You can write your own property delegate: a class with an operator fun getValue, plus setValue for a var. Kotlin calls them for every read and write of the property. This one trims every assigned text and counts the changes:
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
}Inside main:
val form = Form()
form.name = " Ada "
form.name = "Ada L. "
println("[${form.name}] after ${form.nameField.changes} changes")Output:
[Ada L.] after 2 changesChallenge
EasyThe interface Storage (with put, get and keys) and its implementation MapStorage are supplied. Write two decorators with class delegation, each overriding only put: LoggingStorage(inner) records put a=1 in its read-only log list and forwards the call, and LimitedStorage(inner, max) refuses (returns false) to add a new key once max keys exist, while updates of existing keys are still allowed. Everything else is forwarded with by.
The supplied code wraps a MapStorage in a LoggingStorage and, when the first line is a positive limit, that in a LimitedStorage. It runs commands put a 1, get a and keys, printing ok, refused, the value or missing, and the key list, and finally log: put a=1, ....
Your code goes in LoggingStorage.kt, LimitedStorage.kt, Storage.kt and MapStorage.kt. Main.kt holds the supplied input/output code and cannot be edited.
Try it yourself
fun main() {
// Supplied input/output code: keep it as it is
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" })
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesPrimary Constructorsinit Blocks and ValidationSecondary ConstructorsRecap - Library Card4Inheritance
Open Classes and SubclassesOverriding Memberssuper and ConstructorsAbstract ClassesRecap - Staff Directory2Properties and Encapsulation
Custom AccessorsVisibility ModifiersPrivate Setterslazy and lateinitRecap - Bank Account5Interfaces
Declaring InterfacesDefault MethodsMultiple InterfacesInterfaces as TypesRecap - Payment Methods8Generics
Generic FunctionsGeneric ClassesType ConstraintsVariance With in and outRecap - Priority Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Kotlin playground