Skip to main content

Kotlin Class Delegation

Introduction

Delegation is a design pattern that allows object composition as an alternative to inheritance. Rather than extending a class to reuse its functionality, you delegate the work to another object. Kotlin provides a language-level support for delegation using the keyword by.

Class delegation in Kotlin is a powerful feature that helps you implement the composition over inheritance principle, making your code more flexible and maintainable. It allows a class to implement an interface by delegating all of its members to a specified object.

Understanding the Delegation Pattern

Before we dive into Kotlin's implementation, let's understand what delegation means in programming:

  • Inheritance: "I am a type of something"
  • Delegation: "I use something to do my work"

Delegation promotes composition over inheritance, which can lead to more flexible and maintainable code.

Basic Syntax of Class Delegation

The basic syntax for class delegation in Kotlin is:

kotlin
interface Base {
fun print()
}

class BaseImpl(val x: Int) : Base {
override fun print() {
println(x)
}
}

class Derived(b: Base) : Base by b

fun main() {
val b = BaseImpl(10)
val derived = Derived(b)

derived.print() // Prints: 10
}

In this example:

  • Base is an interface with a print() function
  • BaseImpl is a concrete implementation of Base
  • Derived class implements Base by delegating to the provided instance b
  • When we call derived.print(), the call is delegated to the implementation in BaseImpl

How Delegation Works

When you use the by keyword, Kotlin automatically generates all the methods that forward to the delegated object. Behind the scenes, Kotlin creates wrapper methods in the Derived class that call the corresponding methods on the delegated object.

It's as if Kotlin generates this code for you:

kotlin
class Derived(private val b: Base) : Base {
override fun print() {
b.print()
}
}

This saves you from writing repetitive delegation code.

Overriding Delegated Methods

You can override methods from the delegated interface if you need to modify behavior:

kotlin
interface Base {
fun printMessage()
fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
// This overrides the method from the delegate
override fun printMessage() { print("abc") }
}

fun main() {
val b = BaseImpl(10)
val derived = Derived(b)

derived.printMessage() // Prints: abc
derived.printMessageLine() // Prints: 10
}

In this example:

  • printMessage() is overridden in Derived, so the delegated implementation is not used
  • printMessageLine() is not overridden, so it uses the delegated implementation

Multiple Interface Delegation

Kotlin allows delegating multiple interfaces to different objects:

kotlin
interface Engine {
fun start()
}

interface Radio {
fun turnOn()
}

class EngineImpl : Engine {
override fun start() {
println("Engine started")
}
}

class RadioImpl : Radio {
override fun turnOn() {
println("Radio turned on")
}
}

class Car(engine: Engine, radio: Radio) : Engine by engine, Radio by radio

fun main() {
val car = Car(EngineImpl(), RadioImpl())
car.start() // Prints: Engine started
car.turnOn() // Prints: Radio turned on
}

This is a powerful way to compose functionality from different sources without complex inheritance hierarchies.

Property Delegation

In addition to method delegation, Kotlin also supports property delegation:

kotlin
interface User {
val name: String
val age: Int
}

class UserImpl(override val name: String, override val age: Int) : User

class UserInfo(user: User) : User by user {
override val age: Int
get() = super.age + 1 // Modified property
}

fun main() {
val user = UserImpl("John", 30)
val userInfo = UserInfo(user)

println(userInfo.name) // Prints: John (delegated)
println(userInfo.age) // Prints: 31 (modified)
}

In this example, the name property is delegated, while age is overridden with custom logic.

Real-World Example: Window System

Let's see a more practical example of class delegation with a simplified window system:

kotlin
interface WindowFeature {
fun render()
fun handleInput()
}

class ScrollFeature : WindowFeature {
override fun render() {
println("Rendering scrollbars")
}

override fun handleInput() {
println("Handling scroll events")
}
}

class BorderFeature : WindowFeature {
override fun render() {
println("Rendering borders")
}

override fun handleInput() {
println("Handling border resize events")
}
}

class Window(
private val scrollFeature: WindowFeature = ScrollFeature(),
private val borderFeature: WindowFeature = BorderFeature()
) {
// Delegate rendering to specialized feature implementations
fun renderScrollbars() = scrollFeature.render()
fun renderBorders() = borderFeature.render()

// Delegate input handling to specialized feature implementations
fun handleScrollInput() = scrollFeature.handleInput()
fun handleBorderInput() = borderFeature.handleInput()

// Main window methods that use delegated functionality
fun renderAll() {
println("Rendering window background")
renderScrollbars()
renderBorders()
}
}

fun main() {
val window = Window()
window.renderAll()
// Output:
// Rendering window background
// Rendering scrollbars
// Rendering borders

window.handleScrollInput() // Prints: Handling scroll events
}

This approach lets us compose a window from reusable feature components without complex inheritance.

When to Use Class Delegation

Class delegation is particularly useful in the following scenarios:

  1. Implementing the Decorator Pattern: When you need to add behaviors to objects without modifying their code
  2. Composition over Inheritance: When you want to favor object composition over class inheritance
  3. API Adaptation: When adapting one interface to another
  4. Testing: When you need to mock behavior for testing purposes
  5. Feature Composition: When building objects from multiple feature components

Delegation vs. Inheritance

DelegationInheritance
Loose couplingTight coupling
Composition-basedExtension-based
More flexible at runtimeFixed at compile time
Explicit relationshipImplicit relationship
Can delegate to multiple objectsLimited by single inheritance

Summary

Kotlin class delegation provides a built-in solution for implementing the delegation pattern without boilerplate code. Using the by keyword, you can:

  • Implement interfaces by delegating to other objects
  • Override specific methods when needed
  • Delegate to multiple interfaces simultaneously
  • Create more flexible and maintainable code

Class delegation promotes composition over inheritance, leading to more modular and testable code structures. It's an essential tool in the Kotlin developer's toolbox for creating flexible and maintainable applications.

Additional Resources

Exercises

  1. Create an AudioPlayer class that delegates to both MusicPlayer and PodcastPlayer interfaces.
  2. Implement a logging system that uses delegation to log different types of messages in different formats.
  3. Use class delegation to create a caching layer for a data repository.
  4. Implement a shape drawing system where a CompositeShape delegates to multiple other shapes.
  5. Design a notification system that delegates to different notification channels (email, SMS, push).


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)