שליחה דינמית
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Kotlin של Coddy. שיעור 27 מתוך 57.
פולימורפיזם פירושו שקריאה אחת יכולה להתנהג בדרכים רבות. כשקוראים למתודה שנדרסה דרך טיפוס של מחלקת־על או ממשק, Kotlin בודקת את המחלקה בפועל של האובייקט בזמן ריצת התוכנית ומפעילה את הגרסה של אותה מחלקה. זהו שיגור דינמי:
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"
}בתוך main:
val barn: List<Animal> = listOf(Dog("Rex"), Cow("Bess"), Dog("Fido"))
for (a in barn) println(a.speak())פלט:
Rex: Woof
Bess: Moo
Fido: Woofקוד הקריאה אינו משתנה כשמופיעה תת־מחלקה חדשה. totalArea שלהלן נכתב עבור כל Shape, ולכן הוא עובד גם עם Triangle, אף שאיש לא שינה אותו:
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() }בתוך main:
println(totalArea(listOf(Square(2.0), Triangle(3.0, 4.0))))פלט:
10.0השליחה מתבצעת לפי האובייקט, ולא לפי המשתנה. הקצאת אובייקט אחר לאותו משתנה משנה איזה קוד יפעל בקריאה הבאה:
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() + "!"
}בתוך main:
var mode: Mode = Normal()
println(mode.format("hello"))
mode = Shout()
println(mode.format("hello"))פלט:
hello
HELLO!פולימורפיזם מחליף בדיקות כמו if (kind == "dog") שאחרת היו חוזרות בכל מקום שבו משתמשים בבעלי חיים. כל מחלקה שומרת על ההתנהגות שלה, והוספת סוג של בעל חיים פירושה הוספת מחלקה אחת:
// ללא פולימורפיזם: כל פונקציה בודקת את הסוג
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }
// עם פולימורפיזם: הסוג החדש מביא איתו התנהגות משלו
class Sheep(name: String) : Animal(name) {
override fun sound() = "Baa"
}אתגר
קלCharacter(name, hp) היא מחלקה פתוחה. takeHit(damage) מפחיתה את הנזק מ־hp (לעולם לא מתחת ל־0), ו־status() מחזירה Ada: 30 hp או Ada: down כשערכו 0. אפשר לקרוא את hp מכל מקום, אך ניתן לשנות אותו רק בתוך Character. השלימו את מחלקות המשנה באמצעות דריסה של takeHit והעברת הנזק שהשתנה אל super.takeHit: Warrior (40 hp) סופגת חצי מהנזק, בעיגול כלפי מטה; ל־Mage (25 hp) יש מגן שסופג את הפגיעה הראשונה כולה; Rogue (30 hp) מתחמקת מכל פגיעה שנייה שהיא סופגת (השנייה, הרביעית, ...).
הקוד שסופק קורא את החוליה בתור warrior:Ada,mage:Bo,rogue:Cy ואז פוגע ב־all 10 או ב־Ada 12. הוא קורא ל־takeHit דרך הטיפוס Character ולבסוף מדפיס את status() של כל דמות.
כתבו את הקוד שלכם בקבצים Character.kt, Warrior.kt, Mage.kt ו־Rogue.kt. הקובץ Main.kt מכיל את קוד הקלט/פלט שסופק, ואי אפשר לערוך אותו.
נסו בעצמכם
fun main() {
// קוד הקלט/פלט שסופק: יש להשאיר אותו כפי שהוא
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())
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1מחלקות ואובייקטים
עבודה עם קבציםהגדרת מחלקותבנאים ראשייםבלוקי init ואימות נתוניםבנאים משנייםסיכום – כרטיס ספרייה3אובייקטים ואובייקטים נלווים
הצהרות אובייקטאובייקטים נלוויםקבועים עם constביטויי אובייקטסיכום – משרד הכרטיסים6רב־צורתיות
שליחה דינמיתבדיקות טיפוסים והמרות טיפוסים חכמותwhen עם טיפוסיםפונקציות הרחבהסיכום – שומר גן החיותתרגלו בעצמכם: Kotlin אונליין