Class delegation in Kotlin

Written by keivan.esbati | Published 2019/04/29
Tech Story Tags: android | inheritance | delegation | kotlin | composition

TLDRvia the TL;DR App

TL;DR You are fine the way you are, you can skip this article.

Before we start, let’s see how class delegation looks like in Kotlin:

interface Flyable {
    fun fly()
}

interface Movable {
    fun move()
}

class Aircraft(
    liftMechanism: Flyable, 
    propulsion: Movable
): Flyable by liftMechanism, Movable by propulsion

So what does this code do? This class represents an aircraft which is a vehicle capable of moving and flying. Now, this could be done through hierarchy, right? so why going through all the trouble and define it using delegation? Brace yourself because that’s all we are going to talk about in this article.

Hierarchy

If you are coming from Java or another early language you probably have a good knowledge of inheritance. To define the models we usually find the commonality between them to define a base class, then we define the derived classes based on differences, creating a class hierarchy.

To put it simply we are trying to define a family tree based on what the models are. For example, an aircraft is a vehicle which moves and flies and in turn, a glider would be an aircraft that moves without any engine. So you define your models somehow like this:

interface Aircraft {
    fun fly()
    fun move ()
}

interface WingedAircraft: Aircraft {
    override fun fly() = print("Adjust wing angle..")
}

interface RotorCraft: Aircraft {
    override fun fly() = print("Start spinning the blades..")
}

interface UnpoweredVehicle: Aircraft {
    override fun move() = print("Just hanging up here..")
}

interface PropelledVehicle: Aircraft {
    override fun move() = print("Starting engine..")
}

Then we can derive classes like this:

class Glider: WingedAircraft, UnpoweredVehicle
class Jet: WingedAircraft, PropelledVehicle
class Gyroglider: RotorCraft, UnpoweredVehicle
class Helicopter: RotorCraft, PropelledVehicle

So far so familiar, right? With the help of interfaces with default implementation, we can create something like multiple inheritances in Kotlin. It is indeed good; so is there really any problem left to be solved by class delegation? Is there any room left for improvement? To see that, first we have to dive deeper into the hierarchy.

Polymorphism through Inheritance

So what is inheritance all about? It is polymorphism, it enables you to define a class through what it is or what it is derived from. So if a class is derived from Jet it is a jet, now based on what it is, it can evolve from a jet to a fighter or a drone. This is good but at which level you are going to add this new trait? Which entities would be your parents? Jet? Winged and Propelled?

You probably would end up with a class like this:

class Fighter: WingedAircraft, PropelledVehicle, CombatAircraft

, and someone else would end up like this

class Fighter: Jet()

, and someone else (definitely not me) might end up like this

class Fighter: Aircraft

As you can see we are already looking at a family tree.

The Problem

So let’s make our life harder by introducing a problem. Let’s say we want to add a new type of fly mechanism to the mix.

interface AerostatsAircraft: Aircraft {
    override fun fly() = print("Start floating..")
}

Now we can have aircraft that are flying using hot air like balloons. Of course, how these aircraft move define their parents:

class Balloon: AerostatsAircraft, UnpoweredVehicle
class Zeppelin: AerostatsAircraft, PropelledVehicle

So by introducing a new type, the number of sub-classes raise from 4 to 6, now that is not a rational complexity for a simple change, another Propulsion type would increase the number of sub-classes from 6 to 9 and afterward it only gets worse, introducing a new trait alone, like how aircraft are piloted can increase the complexity twice. So here we need another way to model our system.

Polymorphism through Composition

Composition simply is the act of defining an entity through its behavior. It does get even easier because you create a class by looking at what components it has instead of thinking what it is (a hard question no matter the context). For example, you can define an aircraft based on how it flies:

interface Flyable {
    fun fly()
}

class FixedWing: Flyable {
    override fun fly() = print("Adjust wing angle..")
}

class RotaryWing: Flyable {
    override fun fly() = print("Start spinning the blades..")
}

class Aircraft(private val liftMechanism: Flyable): Flyable {
    override fun fly() {
        liftMechanism.fly()
    }
}

or you might define it based on how it moves:

interface Movable {
    fun move()
}

class Unpowered: Movable {
    override fun move() = print("Just hanging up here..")
}

class Propelled: Movable {
    override fun move() = print("Starting engine..")
}

class Aircraft(private val propulsion: Movable): Movable {
    override fun move() {
        propulsion.move()
    }
}

now to extend it to every aircraft you can simply say:

class Aircraft(
    private val liftMechanism: Flyable,
    private val propulsion: Movable
): Flyable, Movable {
    
    override fun fly() {
        liftMechanism.fly()
    }
    
    override fun move() {
        propulsion.move()
    }
}

Now that we’ve built our aircraft let’s see how adding the new type of fly mechanism impacts our class:

class HotAir: Flyable {
    override fun fly() = print("Start floating..")
}

val balloon = Aircraft(HotAir(), Unpowered())
val zeppelin = Aircraft(HotAir(), Propelled())

Not that much, huh? What we were doing here was flattening the hierarchy tree through composing classes by defining their features. It’s after all the same polymorphism and interfaces; What has changed is not the technology we use, but the way we think.

Kotlin class delegation

So what does Kotlin do? It provides a language keyword so you can skip all the boring part:

class Aircraft(
    liftMechanism: Flyable,
    propulsion: Movable
): Flyable by liftMechanism, Movable by propulsion

Kotlin class delegation is by no means magic, it’s just a synthetic sugar (which I appreciate) over composition, which makes composition faster and easier. If anything is deserved to be called magic it’s composition itself which makes life and programming easier.

Disclaimer: This article is not a comparison between inheritance and composition, for that there are a lot of good resources about composition over inheritance but this is not one of them.


Published by HackerNoon on 2019/04/29