Delegation in Kotlin
Delegation in Kotlin lets an object outsource behavior to another object, favoring composition over inheritance.
- For unrelated classes – Shares behavior between classes with no inheritance relationship (
class Printer by networkPrinter). - For related classes – Avoids repeated instance creation through:
- Property delegation (
val db by lazy { Database() }) - Interface implementation reuse (
class LoggingRepository(repo: Repository) : Repository by repo)
- Property delegation (
- For shared behavior – Manages cross-cutting concerns (e.g., logging, caching) without inheritance pollution.
Delegation vs Inheritance
| Inheritance | Delegation | |
|---|---|---|
| Relationship | IS-A (vertical hierarchy) | HAS-A / USES-A (horizontal association) |
| Best suited for | Modeling type hierarchies with shared core logic | Sharing behavior across unrelated types |
| Usage in Kotlin | open class, override for subclassing |
by keyword for interface and property delegation |
Key Features
- Sharing behavior between unrelated classes via
bydelegation - Encapsulating responsibilities in helper classes, without inheritance
-
Property management using built-in delegates:
lazy– Deferred initializationobservable– Observes and reacts to changesvetoable– Conditionally accepts value changes
- Interface implementation reuse via delegation (less boilerplate)
- Custom delegates – Enables clean, reusable property logic
- Boilerplate reduction – No need to manually forward method/property calls
- Runtime flexibility – Delegates can be replaced or configured dynamically
How Delegation Works in Kotlin
Interface Delegation with by
Kotlin enables interface delegation using the by keyword, allowing a class to delegate interface implementation to another instance.
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println("Log: $message")
}
class Service(logger: Logger) : Logger by logger {
fun performTask() {
log("Task performed") // Delegated call
}
}
fun main() {
val service = Service(ConsoleLogger())
service.performTask() // Output: Log: Task performed
}
Without Delegation: Manual Forwarding
Without the by keyword, you must manually forward each interface method:
class ServiceWithoutDelegation(private val logger: Logger) : Logger {
override fun log(message: String) = logger.log(message) // Manual forwarding
fun performTask() {
log("Task performed")
}
}
Kotlin's delegation eliminates this boilerplate while maintaining type safety.
Property Delegation in Kotlin
Built-in Delegates
Kotlin provides several standard property delegates:
// Lazy initialization
val lazyValue: String by lazy {
println("Computed!")
"Hello"
}
fun main() {
println(lazyValue) // Prints "Computed!" then "Hello"
println(lazyValue) // Only prints "Hello"
}
// Observable properties
import kotlin.properties.Delegates
var name: String by Delegates.observable("initial") { _, old, new ->
println("Changed from $old to $new")
}
fun main() {
name = "first" // Prints: Changed from initial to first
name = "second" // Prints: Changed from first to second
}
Custom Property Delegates
Create custom delegates by implementing getValue and setValue:
import kotlin.reflect.KProperty
class UpperCaseDelegate {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value.uppercase()
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value
}
}
class Example {
var text: String by UpperCaseDelegate()
}
fun main() {
val example = Example()
example.text = "delegation"
println(example.text) // Output: DELEGATION
}