Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
24: Delegation with by
A few years ago, I was reviewing a pull request from a developer who was trying to implement a "Decorator" pattern to add logging to a complex Data Repository. He had created a wrapper class that implemented the same interface as the repository, but because the interface had twenty different methods, he had to manually override every single one of them just to call the underlying implementation. It was a sea of boilerplate code—hundreds of lines of repository.doSomething() inside this.doSomething(). It was tedious to read and a nightmare to maintain. That's when I pointed him toward the by keyword.
Cutting out the boilerplate with Class Delegation
In most languages, if you want to wrap a class to add some extra behavior without inheriting from it (composition), you end up writing those annoying forwarding methods. Kotlin solves this with class delegation. By using the by keyword in the class header, you tell the compiler: "Implement this interface, but instead of making me write the logic, just forward all the calls to this other object."
interface Printer {
fun printMessage(msg: String)
fun printError(err: String)
}
class RealPrinter : Printer {
override fun printMessage(msg: String) = println("Printing: $msg")
override fun printError(err: String) = println("Error: $err")
}
// This class implements Printer, but delegates everything to 'base'
class LoggingPrinter(base: Printer) : Printer by base {
override fun printMessage(msg: String) {
println("Log: About to print a message...")
// We can still call the delegated implementation if we want,
// but usually, we just override the specific method we care about.
base.printMessage(msg)
}
}
Notice that LoggingPrinter didn't have to override printError. Because of by base, the compiler automatically generated that forwarding method for us. I love this because it gives you the power of inheritance (the type is still a Printer) with the flexibility of composition. You aren't locked into a rigid class hierarchy; you're just wrapping behavior.
Handing off property logic to the 'by' keyword
Delegation isn't just for classes; it's incredibly powerful for properties. Property delegation means that the getter and setter of a variable are handled by a separate object. You've probably already used by lazy, which is the most common built-in delegate. It ensures a value is only computed the first time it's accessed.
But you can write your own. Imagine you want a property that logs every time it's changed, or one that validates the data before saving it. Instead of writing the same set(value) logic in ten different variables, you create a delegate class.
import kotlin.reflect.KProperty
class TrimDelegate {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
println("Trimming whitespace from ${property.name}")
value = newValue.trim()
}
}
class UserProfile {
// Now, any string assigned to 'username' is automatically trimmed
var username: String by TrimDelegate()
}
The magic here happens in the getValue and setValue operator functions. When you write user.username = " KotlinDev ", Kotlin doesn't actually assign that value to a field in UserProfile. Instead, it calls TrimDelegate.setValue(). It's a clean way to extract cross-cutting concerns—like validation, caching, or logging—out of your business logic and into reusable components.
📋 Practical Task
Exercise: Building a Cached Configuration Manager
You are building a system that loads configuration settings from a slow external database. To avoid hitting the database constantly, you need to implement a property delegate that caches the value after the first read.
Your Task:
- Create a class called
CachedConfigDelegate. - Implement the
getValueoperator function. Inside this function, it should check if a localcachedValuevariable is null. - If it is null, simulate a database call by printing
"Fetching from DB..."and assigning a dummy string (e.g.,"DB_VALUE") to the cache. - If it is not null, print
"Returning cached value"and return the stored value. - Create a
ConfigManagerclass with a propertyapiTokenthat uses yourCachedConfigDelegatevia thebykeyword. - In your
mainfunction, accessapiTokenthree times and verify that the "Fetching from DB..." message only appears once.
There are no comments for now.