Menu
Coddy logo textTech

Dispatch dynamique

Fait partie de la section Programmation orientée objet du Journey Kotlin de Coddy. Leçon 27 sur 57.

Le polymorphisme signifie qu’un même appel peut se comporter de nombreuses façons. Lorsque vous appelez une méthode redéfinie via un type de superclasse ou d’interface, Kotlin examine la classe réelle de l’objet pendant l’exécution du programme et exécute la version de cette classe. C’est la liaison dynamique :

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"
}

À l’intérieur de main :

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

Sortie :

Rex: Woof
Bess: Moo
Fido: Woof

Le code appelant ne change pas lorsqu’une nouvelle sous-classe apparaît. totalArea ci-dessous a été écrit pour n’importe quelle Shape, il fonctionne donc aussi avec Triangle, bien que personne ne l’ait modifié :

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() }

À l’intérieur de main :

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

Sortie :

10.0

Le dispatch examine l’objet, et non la variable. Affecter un autre objet à la même variable modifie le code qui s’exécute lors de l’appel suivant :

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() + "!"
}

Dans main :

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

Sortie :

hello
HELLO!

Le polymorphisme remplace les vérifications comme if (kind == "dog") qui seraient autrement répétées partout où des animaux sont utilisés. Chaque classe conserve son propre comportement, et ajouter un type d’animal signifie ajouter une classe :

// sans polymorphisme : chaque fonction vérifie le kind
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }

// avec polymorphisme : le nouveau kind apporte son propre comportement
class Sheep(name: String) : Animal(name) {
    override fun sound() = "Baa"
}
challenge icon

Défi

Facile

Character(name, hp) est une classe open. takeHit(damage) soustrait les dégâts de hp (sans jamais descendre en dessous de 0), et status() renvoie Ada: 30 hp ou Ada: down lorsque la valeur est 0. hp peut être lu partout, mais ne peut être modifié qu'à l'intérieur de Character. Complète les sous-classes en redéfinissant takeHit et en transmettant les dégâts modifiés à super.takeHit : Warrior (40 hp) ne subit que la moitié des dégâts, arrondie à l'entier inférieur ; Mage (25 hp) possède un bouclier qui absorbe intégralement le premier coup ; Rogue (30 hp) esquive un coup sur deux qu'il reçoit (le 2e, le 4e, ...).

Le code fourni lit le groupe sous la forme warrior:Ada,mage:Bo,rogue:Cy, puis applique all 10 ou Ada 12. Il appelle takeHit via le type Character et affiche finalement chaque status().

Ton code va dans Character.kt, Warrior.kt, Mage.kt et Rogue.kt. Main.kt contient le code fourni pour les entrées et les sorties et ne peut pas être modifié.

Essayez vous-même

fun main() {
    // Code d'entrée/sortie fourni : le garder tel quel
    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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Playground Kotlin