Delegates and DSLs
Property delegation
A delegate handles property reads/writes.
val configuration by lazy {
println("Loading")
"Production"
}
The initializer runs once, on first access.
class User { var name: String by Delegates.observable("Unknown") { _, old, new ->
println("$old -> $new")
} }
Class delegation
interface Logger { fun log(message: String) }
class ConsoleLogger : Logger { override fun log(message: String) = println(message) }
class Repository(logger: Logger) : Logger by logger
Repository forwards Logger members to logger without hand-written boilerplate.
DSL with receiver lambdas
class RequestBuilder {
var url: String = ""
val headers = mutableMapOf<String, String>()
}
fun request(block: RequestBuilder.() -> Unit): RequestBuilder =
RequestBuilder().apply(block)
val request = request {
url = "https://example.com"
headers["Accept"] = "application/json"
}
DSLs are useful when the vocabulary is small and the nesting is clear. Use @DslMarker to prevent accidentally accessing the wrong receiver in nested builders.
✕ DSL hides required values
val request = request { } // empty URL accepted until much later
✓ Require essential values in the function signature
fun request(url: String, block: RequestBuilder.() -> Unit = {}) =
RequestBuilder().apply { this.url = url }.apply(block)
Interview-ready answer
Property delegates reuse get/set behavior, class delegation forwards an interface to another object, and DSLs use receiver lambdas to create a focused language. DSL syntax should not hide validation or required dependencies.