A Pitfall of Delegation in Kotlin

Delegation is a great tool that can help a lot with reducing boilerplate. It is not free of its pitfalls however as we’ll see in this article.

What’s a Mixin?

I’ve been working on a library recently that takes advantage of composition. What I’ve found is that in a lot of the cases inheritance becomes a brittle construct after a while, so I started using mixins.

A mixin is a piece of behavior that provides an interface that other classes can implement, like this:

interface Movable {

    val position: Position

    fun moveTo(position: Position)

    fun moveBy(position: Position) = moveTo(this.position + position)

}

for this example we’ll also need a Position implementation:

data class Position(
        val x: Int,
        val y: Int
) {
    operator fun plus(other: Position): Position {
        return Position(x + other.x, y + other.y)
    }

    operator fun minus(other: Position): Position {
        return Position(x - other.x, y - other.y)
    }
}

In order to call this construct a mixin we need a concrete implementation of Movable that can be delegated to by its implementors:

class MovableImpl(
        initialPosition: Position = Position(0, 0)
) : Movable {

    override var position: Position = initialPosition
        private set

    override fun moveTo(position: Position) {
        this.position = position
    }

}

Note that we can also have mixins that don’t have state in them. Those mixins can be implemented by using an interface only where we provide implementations for all functions. Such a mixin is called a trait.

Now, we can try this out and see that it works as intended:

val movable = MovableImpl()

movable.moveTo(Position(3, 4))
println(movable.position)
// Position(x=3, y=4)

movable.moveBy(Position(1, 2))
println(movable.position)
// Position(x=4, y=6)

All that’s left to do is to start using this in an implementation class. In our case we’re working on an UI library that has UI Components. These components can be moved around so we need to use the Movable mixin:

interface Component : Movable

class ComponentImpl(
        initialPosition: Position = Position(0, 0)
) : Component, Movable by MovableImpl(initialPosition)

We can see that our ComponentImpl didn’t have to override any of the methods in Movable as we delegate the implementation of the Movable interface to MovableImpl. Let’s see how it works:

val component = ComponentImpl()

component.moveTo(Position(3, 4))
println(component.position)
// Position(x=3, y=4)
component.moveBy(Position(1, 2))
println(component.position)
// Position(x=4, y=6)

Congratulations! You’ve written and used a mixin!

It is working, we have nothing to worry about, right?

The Problem

Now let’s take a look at a case where this construct will break down. Let’s introduce a new type of Component, a Container:

interface Container: Component {
    val children: List<Component>
}

A Container is a regular Component in most aspects except it has children. This is very common in UI systems. For example in HTML a div can be considered a container and we can add span elements to it. A span is a regular Component as it can’t have children.

Implementing a Container is pretty similar to implementing a Component.

Our implementation will take its children as a constructor parameter to maintain brevity.

class ContainerImpl(
        initialPosition: Position = Position(0, 0), 
        override val children: List<Component> = listOf()
) : Container, Movable by MovableImpl(initialPosition)

Now we have a problem though. Moving the Container around won’t move its children so we’ll have to override the moveTo function.

We know that Movable has a default implementation for moveBy that will call moveTo, so we don’t have to override that:

class ContainerImpl(
        initialPosition: Position = Position(0, 0),
        override val children: List<Component> = listOf()
) : Container, Movable by MovableImpl(initialPosition) {

    override fun moveTo(position: Position) {
        val diff = position - this.position
        children.forEach {
            it.moveBy(diff)
        }
        moveBy(diff)
    }
}

Now let’s see what happens if we run the previous example with a Container that has a child:

val component = ComponentImpl()
val container = ContainerImpl(children = listOf(component))

println()
println("Positions after moveTo:")
container.moveTo(Position(3, 4))
println(container.position)
println(component.position)

println()
println("Positions after moveBy:")
container.moveBy(Position(1, 2))
println(container.position)
println(component.position)

What will this print? Let’s see:

Positions after moveTo:
Position(x=3, y=4)
Position(x=3, y=4)

Positions after moveBy:
Position(x=4, y=6)
Position(x=3, y=4)

Whoops! What happened here?

When we created an override for moveTo we implemented a custom logic for moving the children of the Component. This works properly, but it doesn’t move the children when we call moveBy.

The problem is that moveBy will indeed call moveTo but that moveTo implementation comes from MovableImpl since we delegated to it and it has no knowledge of our custom moveBy implementation!

Solutions

This problem is a very subtle one. We need some special circumstances for this to happen at all. What also doesn’t help us is that it is not intuitive since there is no explicit code path to follow.

In our case the best is not to use delegation here since we need to use our custom implementation and only delegate by hand:

class ContainerImpl(
        initialPosition: Position = Position(0, 0),
        override val children: List<Component> = listOf(),
) : Container, Movable {

    override var position: Position = initialPosition
        private set

    override fun moveTo(position: Position) {
        val diff = position - this.position
        children.forEach {
            it.moveBy(diff)
        }
        this.position = position
    }
}

another option is to keep the delegation but override moveBy as well to delegate to the proper moveTo:

class ContainerImpl(
        initialPosition: Position = Position(0, 0),
        override val children: List<Component> = listOf(),
        private val movable: Movable = MovableImpl(initialPosition)
) : Container, Movable by movable {

    override fun moveTo(position: Position) {
        val diff = position - this.position
        children.forEach {
            it.moveBy(diff)
        }
        movable.moveTo(position)
    }

    override fun moveBy(position: Position) = moveTo(this.position + position)
}

This latter option is useful if we have state or other methods in our mixin. In our case we have state (position).

Conclusion

Delegation is not a simple topic and it is a prime example for the old adage:

With great power, comes great responsibility.

Whenever we decide to use it we have to put great care into making sure that it works properly.

If you’re interested in the topic take a look at another article I wrote where we explore the concept of delegation in depth.

Now let’s go forth and kode on!