Delegation in Kotlin: Using ‘by’ for Smarter Code Reuse

In Kotlin, delegation is a powerful language feature that promotes code reuse and helps build more maintainable and flexible applications. Using the by keyword, Kotlin allows a class to delegate responsibilities to another object, making code more concise and easier to manage.

What is Delegation in Kotlin?

Delegation in Kotlin is a design pattern where an object, instead of performing one of its stated tasks, delegates that task to an associated helper object. In Kotlin, this is supported directly by the language through the by keyword.

Why Use Delegation?

  • Code Reuse: Delegation enables you to reuse code from multiple classes without using inheritance.
  • Improved Maintainability: By delegating responsibilities, classes become more focused and easier to maintain.
  • Flexibility: Delegation allows you to change the behavior of a class at runtime by swapping out the delegate object.
  • Composition over Inheritance: Favors composition (using objects as instance variables) over inheritance (creating subclasses). This reduces tight coupling and makes systems more flexible.

How to Implement Delegation in Kotlin

Delegation is implemented using the by keyword, followed by an expression of the delegating object.

Implementing a Simple Delegate

Consider an interface SoundBehavior and two implementations, Bark and Meow.


interface SoundBehavior {
    fun makeSound()
}

class Bark : SoundBehavior {
    override fun makeSound() {
        println(\"Woof!\")
    }
}

class Meow : SoundBehavior {
    override fun makeSound() {
        println(\"Meow!\")
    }
}

Now, create a Dog and a Cat class that delegate their sound-making behavior to these implementations:


class Dog(soundBehavior: SoundBehavior) : SoundBehavior by soundBehavior

class Cat(soundBehavior: SoundBehavior) : SoundBehavior by soundBehavior

Here’s how to use these classes:


fun main() {
    val dog = Dog(Bark())
    dog.makeSound() // Output: Woof!

    val cat = Cat(Meow())
    cat.makeSound() // Output: Meow!
}

Delegating to Properties

Kotlin also allows delegation to properties using the by keyword.


import kotlin.properties.Delegates

class Example {
    var p: String by Delegates.observable(\"\") {
        property, oldValue, newValue ->
        println(\"Property \$property changed from \$oldValue to \$newValue\")
    }
}

fun main() {
    val example = Example()
    example.p = \"First\" // Output: Property var p: kotlin.String changed from  to First
    example.p = \"Second\" // Output: Property var p: kotlin.String changed from First to Second
}

In this example:

  • Delegates.observable() is a delegate that notifies listeners about property changes.
  • The p property delegates its behavior to the observable delegate.
  • Whenever p is assigned a new value, the block inside observable is executed.

Implementing Custom Delegates

You can also create custom delegates to encapsulate specific behaviors. Create a delegate class that manages property access:


import kotlin.reflect.KProperty

class StringDelegate {
    private var storedString: String = \"\"

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println(\"Getting value for property \$property.name\")
        return storedString
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println(\"Setting value for property \$property.name to \$value\")
        storedString = value
    }
}

class MyClass {
    var customProperty: String by StringDelegate()
}

fun main() {
    val myObject = MyClass()
    myObject.customProperty = \"Hello, Delegation!\" // Output: Setting value for property customProperty to Hello, Delegation!
    println(myObject.customProperty) // Output: Getting value for property customProperty
                                      //         Hello, Delegation!
}

In this code:

  • StringDelegate class is a custom delegate with getValue and setValue operators.
  • The customProperty in MyClass delegates to an instance of StringDelegate.
  • Whenever customProperty is accessed or modified, the corresponding delegate methods are invoked.

Advanced Delegation Techniques

Delegate with Interface Implementations

You can delegate to an instance that implements an interface, combining interface implementation and delegation.


interface Repository {
    fun getData(): String
}

class LocalRepository : Repository {
    override fun getData(): String {
        return \"Data from local source\"
    }
}

class RemoteRepository : Repository {
    override fun getData(): String {
        return \"Data from remote source\"
    }
}

class DataManager(repository: Repository) : Repository by repository {
    fun processData(): String {
        val data = getData()
        return \"Processed: \$data\"
    }
}

fun main() {
    val localDataManager = DataManager(LocalRepository())
    println(localDataManager.processData()) // Output: Processed: Data from local source

    val remoteDataManager = DataManager(RemoteRepository())
    println(remoteDataManager.processData()) // Output: Processed: Data from remote source
}

Here, DataManager delegates its Repository duties, which are then processed to produce new functionality.

Delegated Properties with Lazy Initialization

Kotlin provides a built-in delegate for lazy initialization of properties, ensuring the property is only computed once when first accessed.


val lazyValue: String by lazy {
    println(\"Computing lazy value...\")
    \"Hello, Lazy Delegation!\"
}

fun main() {
    println(\"Some code here\")
    println(lazyValue) // Output: Computing lazy value...
                         //         Hello, Lazy Delegation!
    println(lazyValue) // Output: Hello, Lazy Delegation! (computed only once)
}

Benefits of Using Delegation in Kotlin

  • Reduced Boilerplate: Delegation eliminates boilerplate code by automatically forwarding calls to the delegate object.
  • Improved Code Organization: Encourages separation of concerns and better-organized classes.
  • Increased Flexibility: Allows you to easily switch implementations at runtime without modifying the class structure.

Conclusion

Delegation in Kotlin, facilitated by the by keyword, is a powerful feature for promoting code reuse, improving maintainability, and enhancing flexibility. By understanding and utilizing delegation effectively, developers can create more robust and elegant Kotlin applications. Whether you’re delegating to interfaces, properties, or implementing custom delegates, Kotlin’s delegation feature provides a concise and expressive way to manage complex behaviors.