Delegation

Kotlin's delegation patterns — class delegation, property delegation, lazy, observable, and custom delegates

Kotlin supports delegation as a first-class language feature, both for classes and properties.

Class delegation (by)

Delegate interface implementation to another object — composition over inheritance.

interface Printable {
    fun print()
}

class ConsolePrinter : Printable {
    override fun print() = println("Printing to console")
}

// PrinterService delegates Printable to ConsolePrinter
class PrinterService(private val printer: Printable) : Printable by printer

val service = PrinterService(ConsolePrinter())
service.print()  // "Printing to console"

Property delegation

lazy

Initialized on first access, thread-safe by default.

val heavyObject: Expensive by lazy {
    println("Computing...")
    Expensive()                    // computed once, cached
}

// Thread-safety modes
val value by lazy(Lazy.ThreadSafetyMode.NONE) { compute() } // no synchronization
val value by lazy(Lazy.ThreadSafetyMode.SYNCHRONIZED) { compute() } // default
val value by lazy(Lazy.ThreadSafetyMode.PUBLICATION) { compute() } // safe but may compute multiple times

observable

Notifies on property change.

import kotlin.properties.Delegates

var name: String by Delegates.observable("<unset>") { _, old, new ->
    println("Name changed from $old to $new")
}

name = "Alice"  // prints: Name changed from <unset> to Alice
name = "Bob"    // prints: Name changed from Alice to Bob

vetoable

Reject property changes based on a condition.

var age: Int by Delegates.vetoable(0) { _, old, new ->
    new >= 0    // only accept non-negative values
}

age = 25   // accepted
age = -5   // rejected, age stays 25

notNull

Late initialization for non-nullable properties (throws before set).

var value: String by Delegates.notNull()

// value.length  // throws IllegalStateException
value = "hello"
println(value)   // "hello"

Map delegation

Use a map as a property delegate — common for JSON/dynamic data.

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

val user = User(mapOf("name" to "Alice", "age" to 30))
println(user.name)  // "Alice"
println(user.age)   // 30

// Mutable map for var properties
class MutableUser(map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int by map
}

val mutableUser = MutableUser(mutableMapOf("name" to "Bob", "age" to 25))
mutableUser.name = "Charlie"  // updates the map

Custom property delegate

Implement ReadOnlyProperty or ReadWriteProperty.

import kotlin.reflect.KProperty

class Example {
    // Custom read-only delegate
    class UpperCase(initial: String) : ReadOnlyProperty<Any?, String> {
        private var value = initial
        override fun getValue(thisRef: Any?, property: KProperty<*>): String {
            return value.uppercase()
        }
    }

    // Custom read-write delegate
    class Logging<T>(initial: T) : ReadWriteProperty<Any?, T> {
        private var value = initial
        override fun getValue(thisRef: Any?, property: KProperty<*>): T {
            println("Getting ${property.name}: $value")
            return value
        }
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            println("Setting ${property.name} from ${this.value} to $value")
            this.value = value
        }
    }
}

// Usage
val greeting by Example.UpperCase("hello")        // "HELLO"
var count by Example.Logging(0)                     // logs on get/set

Delegated properties with provideDelegate

For controlling delegate creation at the time of property registration.

class ResourceDelegate<T>(private val key: String) : ReadOnlyProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        // look up resource by key
        @Suppress("UNCHECKED_CAST")
        return loadResource(key) as T
    }
}

class ResourceDelegateProvider<T>(private val key: String) : PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> {
    override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, T> {
        // called at property registration time, not on each access
        checkPropertyAccess(property.name)
        return ResourceDelegate(key)
    }
}

val config by ResourceDelegateProvider<String>("app.config")