Dynamisches Dispatching
Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 27 von 57.
Polymorphismus bedeutet, dass ein Aufruf sich auf viele Arten verhalten kann. Wenn du eine überschriebene Methode über einen Oberklassen- oder Schnittstellentyp aufrufst, betrachtet Kotlin während der Programmausführung die tatsächliche Klasse des Objekts und führt die Version dieser Klasse aus. Dies ist dynamischer 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"
}Innerhalb von main:
val barn: List<Animal> = listOf(Dog("Rex"), Cow("Bess"), Dog("Fido"))
for (a in barn) println(a.speak())Ausgabe:
Rex: Woof
Bess: Moo
Fido: WoofDer aufrufende Code ändert sich nicht, wenn eine neue Unterklasse hinzukommt. totalArea unten wurde für jede Shape geschrieben, daher funktioniert er auch mit Triangle, obwohl niemand ihn geändert hat:
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() }Innerhalb von main:
println(totalArea(listOf(Square(2.0), Triangle(3.0, 4.0))))Ausgabe:
10.0Der Dispatch betrachtet das Objekt, nicht die Variable. Wenn du derselben Variable ein anderes Objekt zuweist, ändert sich, welcher Code beim nächsten Aufruf ausgeführt wird:
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() + "!"
}Innerhalb von main:
var mode: Mode = Normal()
println(mode.format("hello"))
mode = Shout()
println(mode.format("hello"))Ausgabe:
hello
HELLO!Polymorphismus ersetzt Prüfungen wie if (kind == "dog"), die andernfalls überall dort wiederholt würden, wo Tiere verwendet werden. Jede Klasse behält ihr eigenes Verhalten, und das Hinzufügen einer Tierart bedeutet, eine Klasse hinzuzufügen:
// ohne Polymorphismus: jede Funktion prüft die Art
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }
// mit Polymorphismus: die neue Art bringt ihr eigenes Verhalten mit
class Sheep(name: String) : Animal(name) {
override fun sound() = "Baa"
}Aufgabe
EinfachCharacter(name, hp) ist eine offene Klasse. takeHit(damage) zieht den Schaden von hp ab (niemals unter 0), und status() gibt bei 0 Ada: 30 hp oder Ada: down zurück. hp kann überall gelesen, aber nur innerhalb von Character geändert werden. Vervollständige die Unterklassen, indem du takeHit überschreibst und den geänderten Schaden an super.takeHit weitergibst: Warrior (40 hp) erleidet die Hälfte des Schadens, abgerundet; Mage (25 hp) besitzt einen Schild, der den gesamten ersten Treffer absorbiert; Rogue (30 hp) weicht jedem zweiten Treffer aus, den er erhält (dem 2., 4. ...).
Der bereitgestellte Code liest die Gruppe als warrior:Ada,mage:Bo,rogue:Cy ein und führt anschließend all 10 oder Ada 12 als Treffer aus. Er ruft takeHit über den Typ Character auf und gibt schließlich jedes status() aus.
Dein Code gehört in Character.kt, Warrior.kt, Mage.kt und Rogue.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und kann nicht bearbeitet werden.
Probier es selbst
fun main() {
// Gelieferter Ein-/Ausgabecode: belasse ihn wie er ist
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())
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenPrimärkonstruktoreninit-Blöcke und ValidierungSekundärkonstruktorenRückblick – Bibliotheksausweis4Vererbung
Offene Klassen und UnterklassenÜberschreiben von Membernsuper und KonstruktorenAbstrakte KlassenRückblick – Mitarbeiterverzeichnis2Eigenschaften und Kapselung
Benutzerdefinierte ZugriffsmethodenSichtbarkeitsmodifikatorenPrivate Setterlazy und lateinitWiederholung – Bankkonto5Schnittstellen
Schnittstellen deklarierenStandardmethodenMehrere SchnittstellenSchnittstellen als TypenRückblick – Zahlungsmethoden8Generics
Generische FunktionenGenerische KlassenTypbeschränkungenVarianz mit in und outRückblick – Prioritätswarteschlange11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihen3Objekte und Companion-Objekte
ObjektdeklarationenCompanion-ObjekteKonstanten mit constObjektausdrückeRückblick – Fahrkartenschalter6Polymorphie
Dynamisches DispatchingTypprüfungen und Smart Castswhen mit TypenErweiterungsfunktionenRückblick – ZoowärterÜbe selbstständig: Kotlin-Playground