מחלקות מופשטות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Kotlin של Coddy. שיעור 20 מתוך 57.
מחלקת abstract היא מחלקת-על שאי אפשר ליצור מופעים שלה בפני עצמה. היא יכולה להצהיר על איברי abstract ללא גוף, וכל תת-מחלקה קונקרטית חייבת לממש אותם:
abstract class Shape(val name: String) {
abstract fun area(): Double
fun describe() = "$name with area ${area()}"
}
class Circle(val r: Double) : Shape("circle") {
override fun area() = 3.14 * r * r
}
class Rect(val w: Double, val h: Double) : Shape("rect") {
override fun area() = w * h
}בתוך main:
val shapes: List<Shape> = listOf(Circle(1.0), Rect(2.0, 3.0))
for (s in shapes) println(s.describe())פלט:
circle with area 3.14
rect with area 6.0מחלקה מופשטת פתוחה גם בלי מילת המפתח open, וגם החברים המופשטים שלה פתוחים, כך שמחלקות יורשות צריכות לכתוב רק override. יצירת המחלקה המופשטת עצמה אינה מתקמפלת, וגם מחלקה יורשת שמשמיטה חבר מופשט חייבת להיות מופשטת. השתמשו במחלקה מופשטת כאשר למחלקת־העל אין התנהגות הגיונית משלה עבור חלק מהחברים:
// val s = Shape("blob") // שגיאה: אי אפשר ליצור מופע של מחלקה מופשטת
// class Blob : Shape("blob") // שגיאה: Blob אינה מממשת את area()
abstract class Polygon(name: String) : Shape(name) // תקין: עדיין מופשטתלמחלקה מופשטת יכולים להיות בנאי, מצב ומתודות רגילות, שתתי־המחלקות יורשות. שימוש נפוץ הוא הליך קבוע שהשלבים שלו משתנים, מתודת תבנית: מתודת מחלקת־העל מריצה את השלבים, ותתי־המחלקות מממשות את המתודות המופשטות:
abstract class Report(val title: String) {
fun render() = "== $title ==\n${body()}\n(${footer()})"
abstract fun body(): String
open fun footer() = "end"
}
class Sales(val total: Int) : Report("Sales") {
override fun body() = "total: $total"
}
class Stock(val items: Int) : Report("Stock") {
override fun body() = "items: $items"
override fun footer() = "counted today"
}בתוך main:
println(Sales(420).render())
println(Stock(17).render())פלט:
== Sales ==
total: 420
(end)
== Stock ==
items: 17
(counted today)מאפיינים מופשטים פועלים באותו אופן. תת-המחלקה מספקת את הערך כמאפיין של הבנאי, כמאפיין שמור או כ-getter:
abstract class Worker(val name: String) {
abstract val monthly: Int
fun yearly() = monthly * 12
}
class Salaried(name: String, override val monthly: Int) : Worker(name)
class Hourly(name: String, val hours: Int, val rate: Int) : Worker(name) {
override val monthly: Int
get() = hours * rate
}בתוך main:
val staff = listOf(Salaried("Ada", 4000), Hourly("Bo", 80, 25))
for (w in staff) println("${w.name}: ${w.yearly()}")פלט:
Ada: 48000
Bo: 24000אתגר
קלDiscount היא מחלקה מופשטת. היא מצהירה על המתודה המופשטת amount(price) ומממשת את apply(price), שמחזירה את המחיר פחות amount(price), אך לא פחות מ־0. השלימו את מחלקות המשנה: PercentOff(percent) מפחיתה את האחוז הזה מהמחיר (בעיגול כלפי מטה), FixedOff(value) מפחיתה ערך קבוע, ו־SpendMore(threshold, off) מפחיתה את off רק כאשר המחיר הוא לפחות threshold.
הקוד שסופק קורא את המחיר ואז את ההנחות p,10, f,300 או s,500,50, מחיל כל אחת מהן על המחיר ומדפיס 1000 -> 900.
הקוד שלכם ייכתב בקבצים Discount.kt, PercentOff.kt, FixedOff.kt ו־SpendMore.kt. הקובץ Main.kt מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.
נסו בעצמכם
fun main() {
// קוד קלט/פלט שסופק: השאירו אותו כפי שהוא
val input = generateSequence(::readLine).toList()
val price = input[0].toInt()
for (line in input.drop(1)) {
val p = line.split(",")
val discount: Discount = when (p[0]) {
"p" -> PercentOff(p[1].toInt())
"f" -> FixedOff(p[1].toInt())
else -> SpendMore(p[1].toInt(), p[2].toInt())
}
println("$price -> ${discount.apply(price)}")
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1מחלקות ואובייקטים
עבודה עם קבציםהגדרת מחלקותבנאים ראשייםבלוקי init ואימות נתוניםבנאים משנייםסיכום – כרטיס ספרייהתרגלו בעצמכם: Kotlin אונליין