404 Not Found

404 Not Found


nginx

Kotlin Delegation and Property Delegation Explained

Delegation is Kotlin's killer language feature — one line for thread-safe lazy initialization, one line for property change monitoring. Charlie uses class delegation to replace Decorator pattern boilerplate, and property delegation to auto-persist OrderProcessor configuration properties.

1. What You'll Learn


2. A Real Architect's Story

(1) Pain Point: Decorator Pattern Boilerplate

Charlie used the Java Decorator pattern to add logging and caching to OrderRepository. Every layer required 20+ lines of delegation methods — the interface has 8 methods, and 3 decorator layers meant 480 lines of pure delegation code.

(2) Kotlin's Class Delegation Solution

KOTLIN
// Java: 20+ lines per method per decorator
class LoggedRepository implements OrderRepository {
    private final OrderRepository delegate;
    @Override public Order findById(String id) { log(); return delegate.findById(id); }
    // ... repeat for all 8 methods
}

// Kotlin: 1 line delegates ALL methods
class LoggedRepository(
    private val delegate: OrderRepository
) : OrderRepository by delegate  // Auto-delegates all methods!

One line replaces 20+ lines of method delegation — only override the methods you want to enhance.


3. Class Delegation (by)

(1) Basic Syntax

KOTLIN
interface OrderRepository {
    fun findById(id: String): Order?
    fun save(order: Order)
    fun findAll(): List<Order>
}

class InMemoryRepository : OrderRepository {
    private val storage = mutableMapOf<String, Order>()
    override fun findById(id: String) = storage[id]
    override fun save(order: Order) { storage[order.id] = order }
    override fun findAll() = storage.values.toList()
}

// Class delegation: auto-delegates all interface methods
class LoggedRepository(
    private val inner: OrderRepository
) : OrderRepository by inner {  // All methods delegated!
    // Override only what you want to enhance
    override fun save(order: Order) {
        println("Saving order: ${order.id}")
        inner.save(order)  // Call delegate explicitly
    }
}

(2) Multi-Layer Delegation

KOTLIN
// Layer 1: Caching
class CachedRepository(
    private val delegate: OrderRepository
) : OrderRepository by delegate {
    private val cache = mutableMapOf<String, Order>()

    override fun findById(id: String): Order? {
        return cache.getOrPut(id) { delegate.findById(id)!! }
    }
}

// Layer 2: Logging
class LoggedRepository(
    private val delegate: OrderRepository
) : OrderRepository by delegate {
    override fun save(order: Order) {
        println("Saving: ${order.id}")
        delegate.save(order)
    }
}

// Compose: logging + caching + in-memory
val repo = LoggedRepository(CachedRepository(InMemoryRepository()))

(3) Class Delegation vs Inheritance vs Decorator

Dimension Inheritance Decorator (Java) Class Delegation (Kotlin)
Code volume Low High (boilerplate) Low (by)
Flexibility Single inheritance Multi-layer composition Multi-layer composition
Coupling High Low Low
Runtime Parent class binding Delegate object binding Delegate object binding

4. Property Delegation: by lazy

(1) Basic Usage

KOTLIN
class OrderProcessor {
    // Lazy: computed on first access, thread-safe by default
    val cache: OrderCache by lazy {
        println("Initializing cache...")
        OrderCache(maxSize = 10_000)
    }

    // Lazy with custom thread safety
    val heavyConfig by lazy(LazyThreadSafetyMode.PUBLICATION) {
        loadConfig()  // May be called multiple times, but only first result used
    }
}

(2) lazy Thread Safety Modes

Mode Behavior Performance Use Case
SYNCHRONIZED (default) Single init, double-checked locking Slightly slower Multi-threaded environments
PUBLICATION Multiple threads may compute, only first result used Faster Init with no side effects
NONE No synchronization Fastest Known single-threaded context

5. Property Delegation: observable / vetoable

(1) observable — Change Notification

KOTLIN
import kotlin.properties.Delegates

class Order(var id: String) {
    // observable: notify on every change
    var status: String by Delegates.observable("PENDING") { _, old, new ->
        println("Status changed: $old -> $new")
    }
}

val order = Order("ORD-001")
order.status = "CONFIRMED"  // Prints: Status changed: PENDING -> CONFIRMED
order.status = "SHIPPED"    // Prints: Status changed: CONFIRMED -> SHIPPED

(2) vetoable — Change Interception

KOTLIN
class Order(var id: String) {
    // vetoable: can reject the change
    var total: Double by Delegates.vetoable(0.0) { _, old, new ->
        if (new < 0) {
            println("Rejecting negative total: $new")
            false  // Veto: keep old value
        } else {
            true  // Accept the change
        }
    }
}

val order = Order("ORD-001")
order.total = 299.99  // Accepted
order.total = -50.0   // Rejected: prints warning, keeps 299.99

(3) observable vs vetoable Comparison

Delegate Behavior Return Value Use Case
observable Notify change, don't intercept Unit Logging / auditing
vetoable Can intercept and reject change Boolean Validation / constraints

6. Custom Property Delegation

(1) ReadWriteProperty Interface

KOTLIN
import kotlin.reflect.KProperty

class PersistentProperty<T>(
    private val key: String,
    private val defaultValue: T
) : ReadWriteProperty<Any?, T> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        // Load from persistence (simplified)
        return loadFromStore(key) ?: defaultValue
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        // Save to persistence
        saveToStore(key, value)
    }
}

// Simulated persistence
private val store = mutableMapOf<String, Any?>()
private fun <T> loadFromStore(key: String): T? = store[key] as T?
private fun <T> saveToStore(key: String, value: T) { store[key] = value }

(2) Using Custom Delegates

KOTLIN
class OrderConfig {
    var maxRetries: Int by PersistentProperty("order.max_retries", 3)
    var timeout: Long by PersistentProperty("order.timeout", 5_000L)
    var taxRate: Double by PersistentProperty("order.tax_rate", 0.08)
}

val config = OrderConfig()
println(config.maxRetries)  // 3 (default)
config.maxRetries = 5
println(config.maxRetries)  // 5 (persisted)

(3) Delegate Class Diagram

100%
classDiagram
    class ReadOnlyProperty {
        <<interface>>
        +getValue(thisRef, property): T
    }
    class ReadWriteProperty {
        <<interface>>
        +getValue(thisRef, property): T
        +setValue(thisRef, property, value: T)
    }
    class LazyDelegate {
        +getValue(): T
    }
    class ObservableDelegate {
        +setValue()
    }
    class VetoableDelegate {
        +setValue()
    }
    class CustomDelegate {
        +getValue()
        +setValue()
    }
    ReadOnlyProperty <|-- ReadWriteProperty
    ReadOnlyProperty <|-- LazyDelegate
    ReadWriteProperty <|-- ObservableDelegate
    ReadWriteProperty <|-- VetoableDelegate
    ReadWriteProperty <|-- CustomDelegate

7. Complete Example: OrderProcessor Delegation Architecture

KOTLIN
// ============================================
// OrderProcessor - Delegation Architecture
// Feature: Class delegation + property delegation
// ============================================

import kotlin.properties.Delegates
import kotlin.reflect.KProperty

// --- Class Delegation ---

interface OrderRepository {
    fun findById(id: String): Order?
    fun save(order: Order)
    fun findAll(): List<Order>
}

data class Order(val id: String, var total: Double, var status: String, val customer: String)

class InMemoryRepository : OrderRepository {
    private val storage = mutableMapOf<String, Order>()
    override fun findById(id: String) = storage[id]
    override fun save(order: Order) { storage[order.id] = order }
    override fun findAll() = storage.values.toList()
}

class LoggedRepository(private val inner: OrderRepository) : OrderRepository by inner {
    override fun save(order: Order) {
        println("  [LOG] Saving order: ${order.id} (\$${order.total} USD)")
        inner.save(order)
    }
}

class ValidatedRepository(private val inner: OrderRepository) : OrderRepository by inner {
    override fun save(order: Order) {
        require(order.total >= 0) { "Total must be non-negative" }
        require(order.id.startsWith("ORD-")) { "Invalid order ID format" }
        inner.save(order)
    }
}

// --- Property Delegation ---

private val configStore = mutableMapOf<String, Any?>()

class ConfigProperty<T>(private val key: String, private val default: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        @Suppress("UNCHECKED_CAST")
        return configStore[key] as? T ?: default
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        configStore[key] = value
        println("  [CONFIG] $key = $value")
    }
}

class OrderProcessorConfig {
    var maxOrderValue: Double by ConfigProperty("max_order_value", 100_000.0)
    var defaultTaxRate: Double by ConfigProperty("default_tax_rate", 0.08)
    var enableAuditing: Boolean by Delegates.observable(true) { _, _, new ->
        println("  [CONFIG] Auditing ${if (new) "enabled" else "disabled"}")
    }
    var processingTimeout: Long by Delegates.vetoable(30_000L) { _, _, new ->
        if (new < 1_000) { println("  [CONFIG] Rejecting timeout < 1000ms"); false }
        else true
    }
}

fun main() {
    // Compose: validation + logging + in-memory
    val repo: OrderRepository = ValidatedRepository(LoggedRepository(InMemoryRepository()))

    println("=== Saving Orders ===")
    repo.save(Order("ORD-001", 299.99, "CONFIRMED", "Alice"))
    repo.save(Order("ORD-002", 15_000.00, "PENDING", "Bob"))

    println("\n=== Find Order ===")
    println(repo.findById("ORD-001"))

    // Property delegation
    println("\n=== Configuration ===")
    val config = OrderProcessorConfig()
    println("Max order value: \$${config.maxOrderValue} USD")
    config.defaultTaxRate = 0.10
    config.enableAuditing = false
    config.processingTimeout = 500  // Rejected
    config.processingTimeout = 60_000  // Accepted
    println("Timeout: ${config.processingTimeout}ms")
}

Output:

TEXT
=== Saving Orders ===
  [LOG] Saving order: ORD-001 ($299.99 USD)
  [LOG] Saving order: ORD-002 ($15000.0 USD)

=== Find Order ===
Order(id=ORD-001, total=299.99, status=CONFIRMED, customer=Alice)

=== Configuration ===
Max order value: $100000.0 USD
  [CONFIG] default_tax_rate = 0.1
  [CONFIG] Auditing disabled
  [CONFIG] Rejecting timeout < 1000ms
  [CONFIG] processingTimeout = 60000
Timeout: 60000ms

❓ FAQ

Q What's the difference between class delegation and inheritance?
A Class delegation is composition (has-a); inheritance is subtyping (is-a). Delegation is more flexible — you can replace the delegate at runtime and avoid the tight coupling of inheritance.
Q Does by lazy initialization always happen?
A Not necessarily. If the property is never accessed, initialization never executes. This is lazy's core advantage — avoiding unnecessary initialization overhead.
Q What's the performance of property delegation?
A Each property read/write incurs an extra function call (getValue/setValue), but the JVM's inlining optimization typically eliminates this overhead. For hot paths, measure before deciding.
Q Can a class use multiple by delegation?
A Class delegation delegates one interface (multiple inheritance via multiple interfaces), and property delegation delegates each property independently.
Q Can observable be used for reactive programming?
A Its scope is limited — it only notifies within the same object. For cross-component reactivity, use StateFlow/SharedFlow. observable is better suited for audit logs and simple notifications.
Q Must custom delegates implement ReadWriteProperty?
A Not required. You only need to provide getValue (and setValue for var). ReadWriteProperty is just a convenience interface.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Use by lazy to implement a lazy-initialized database connection that prints "Connecting..." on first access. Hint: val connection by lazy { ... }
  2. Intermediate (⭐⭐): Use class delegation to implement LoggedOrderRepo, overriding only the save method to add logging. Hint: class LoggedOrderRepo(val inner: OrderRepository) : OrderRepository by inner { ... }
  3. Challenge (⭐⭐⭐): Implement a custom TraceDelegate property delegate that prints the property name and value on every read and write. Hint: getValue + setValue operator functions

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏