Class Delegation

Composition Without Boilerplate

Delegation means "let another object do the work". With class A(b: B) : Interface by b the compiler generates every interface method and forwards it to b. You override only what you want to change. It is the decorator pattern without writing forwarding code, and a clean alternative to inheritance.

Example

A list that counts how many items were added, without extending ArrayList.

class CountingList<T>(
    private val inner: MutableList<T> = mutableListOf(),
) : MutableList<T> by inner {
    var addCount = 0
        private set

    override fun add(element: T): Boolean {
        addCount++
        return inner.add(element)
    }

    override fun addAll(elements: Collection<T>): Boolean {
        addCount += elements.size
        return inner.addAll(elements)
    }
}

fun main() {
    val list = CountingList<String>()
    list.add("Kotlin")
    list.addAll(listOf("Java", "Go"))
    list.remove("Go")              // forwarded automatically
    println("List: ${list.toList()}, size: ${list.size}")   // toString() is not delegated
    println("Items ever added: ${list.addCount}")
}

Output

List: [Kotlin, Java], size: 2
Items ever added: 3

Decorating a Service

Example

Add logging around any PaymentGateway implementation.

interface PaymentGateway {
    fun pay(orderId: String, amount: Int): String
    fun refund(orderId: String): String
}

class RazorpayGateway : PaymentGateway {
    override fun pay(orderId: String, amount: Int) = "paid Rs $amount for $orderId"
    override fun refund(orderId: String) = "refunded $orderId"
}

class LoggingGateway(private val target: PaymentGateway) : PaymentGateway by target {
    override fun pay(orderId: String, amount: Int): String {
        println("LOG: pay($orderId, $amount)")
        return target.pay(orderId, amount)
    }
}

fun main() {
    val gateway: PaymentGateway = LoggingGateway(RazorpayGateway())
    println(gateway.pay("ORD-1", 799))
    println(gateway.refund("ORD-1"))   // not logged: plain forwarding
}

Output

LOG: pay(ORD-1, 799)
paid Rs 799 for ORD-1
refunded ORD-1

Tip: "Prefer composition over inheritance" (Effective Java, item 18). Kotlin classes are final by default, and by makes composition as short as inheritance.