Menu
Coddy logo textTech

Dynamic Dispatch

Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 27 of 57.

Polymorphism means that one call can behave in many ways. When you call an overridden method through a superclass or interface type, Kotlin looks at the object's actual class while the program runs and executes that class's version. This is dynamic dispatch:

abstract class Animal(val name: String) {
    abstract fun sound(): String
    fun speak() = "$name: ${sound()}"
}
class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof"
}
class Cow(name: String) : Animal(name) {
    override fun sound() = "Moo"
}

Inside main:

val barn: List<Animal> = listOf(Dog("Rex"), Cow("Bess"), Dog("Fido"))
for (a in barn) println(a.speak())

Output:

Rex: Woof
Bess: Moo
Fido: Woof

The calling code does not change when a new subclass appears. totalArea below was written for any Shape, so it works with Triangle too, although nobody changed it:

interface Shape {
    fun area(): Double
}
class Square(val side: Double) : Shape {
    override fun area() = side * side
}
class Triangle(val base: Double, val height: Double) : Shape {
    override fun area() = base * height / 2
}

fun totalArea(shapes: List<Shape>) = shapes.sumOf { it.area() }

Inside main:

println(totalArea(listOf(Square(2.0), Triangle(3.0, 4.0))))

Output:

10.0

Dispatch looks at the object, not at the variable. Assigning another object to the same variable changes which code runs on the next call:

interface Mode {
    fun format(text: String): String
}
class Normal : Mode {
    override fun format(text: String) = text
}
class Shout : Mode {
    override fun format(text: String) = text.uppercase() + "!"
}

Inside main:

var mode: Mode = Normal()
println(mode.format("hello"))
mode = Shout()
println(mode.format("hello"))

Output:

hello
HELLO!

Polymorphism replaces checks like if (kind == "dog") that would otherwise be repeated wherever animals are used. Each class keeps its own behaviour, and adding a kind of animal means adding one class:

// without polymorphism: every function checks the kind
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }

// with polymorphism: the new kind brings its own behaviour
class Sheep(name: String) : Animal(name) {
    override fun sound() = "Baa"
}
challenge icon

Challenge

Easy

Character(name, hp) is an open class. takeHit(damage) subtracts the damage from hp (never below 0), and status() returns Ada: 30 hp or Ada: down at 0. hp can be read everywhere but only changed inside Character. Complete the subclasses by overriding takeHit and passing the changed damage on to super.takeHit: Warrior (40 hp) takes half the damage, rounded down; Mage (25 hp) has a shield that absorbs the whole first hit; Rogue (30 hp) dodges every second hit it receives (the 2nd, 4th, ...).

The supplied code reads the party as warrior:Ada,mage:Bo,rogue:Cy and then hits all 10 or Ada 12. It calls takeHit through the type Character and finally prints every status().

Your code goes in Character.kt, Warrior.kt, Mage.kt and Rogue.kt. Main.kt holds the supplied input/output code and cannot be edited.

Try it yourself

fun main() {
    // Supplied input/output code: keep it as it is
    val input = generateSequence(::readLine).toList()
    val party = input[0].split(",").map { spec ->
        val (kind, name) = spec.split(":")
        when (kind) {
            "warrior" -> Warrior(name)
            "mage" -> Mage(name)
            else -> Rogue(name)
        }
    }
    for (cmd in input.drop(1)) {
        val (target, damage) = cmd.split(" ")
        for (c in party) {
            if (target == "all" || target == c.name) c.takeHit(damage.toInt())
        }
    }
    for (c in party) println(c.status())
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Kotlin playground