Menu
Coddy logo textTech

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, Ada

Delegation 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: true

Delegated 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 36

You 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 changes
challenge icon

Challenge

Easy

The 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" })
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Kotlin playground