Menu
Coddy logo textTech

מחלקות מופשטות

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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
challenge icon

אתגר

קל

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)}")
    }
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: Kotlin אונליין