Menu
Coddy logo textTech

אובייקטים נלווים

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Kotlin של Coddy. שיעור 13 מתוך 57.

חברים מסוימים שייכים למחלקה כולה ולא למופע יחיד. ב-Kotlin מגדירים אותם בתוך companion object שבמחלקה, וקוראים להם באמצעות שם המחלקה:

class Temperature(val celsius: Double) {
    companion object {
        fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
    }
}

בתוך main:

val boiling = Temperature.fromFahrenheit(212.0)
println(boiling.celsius)

פלט:

100.0

fromFahrenheit היא פונקציית מפעל: פונקציה שיוצרת מופעים. בניגוד לבנאי, יש לה שם תיאורי, והיא יכולה להחזיר null במקום אובייקט. עם private constructor, פונקציית המפעל היא הדרך היחידה ליצור מופע, ולכן היא יכולה לבדוק ולנקות תחילה את הקלט:

class Username private constructor(val text: String) {
    companion object {
        fun of(raw: String): Username? {
            val clean = raw.trim().lowercase()
            return if (clean.length in 3..12) Username(clean) else null
        }
    }
}

בתוך main:

println(Username.of("  Ada_L ")?.text)
println(Username.of("x")?.text)
// Username("x")   // שגיאה: הבנאי פרטי

פלט:

ada_l
null

האובייקט הנלווה שייך למחלקה, ולכן הוא יכול לקרוא לבנאי הפרטי, והקוד של המחלקה עצמה משתמש באיברי האובייקט הנלווה ללא קידומת. כאן כל כרטיס מקבל את המספר הבא ממונה אחד שכל הכרטיסים חולקים:

class Ticket(val holder: String) {
    val number = nextNumber++

    companion object {
        private var nextNumber = 1
        val issued: Int
            get() = nextNumber - 1
    }
}

בתוך main:

val a = Ticket("Ada")
val b = Ticket("Bo")
println("${a.holder} #${a.number}, ${b.holder} #${b.number}")
println("issued: ${Ticket.issued}")

פלט:

Ada #1, Bo #2
issued: 2

ב-Kotlin אין מילת מפתח static. החברים של האובייקט הנלווה ממלאים את התפקיד הזה: עותק אחד עבור המחלקה כולה, שאליו ניגשים באמצעות ClassName.member. כמו כל אובייקט, האובייקט הנלווה נוצר פעם אחת, כשהמחלקה נמצאת בשימוש לראשונה. שימו בו דברים שאינם תלויים במופע מסוים, כמו פונקציות ליצירת מופעים ומונים משותפים; כל דבר שקורא נתונים של מופע נשאר חבר רגיל.

class User private constructor(val name: String) {
    fun greet() = "hi $name"             // דורש מופע: user.greet()

    companion object {
        fun guest() = User("guest")      // שייך למחלקה: User.guest()
    }
}
challenge icon

אתגר

קל

השלימו את המחלקה Coupon. הבנאי שלה פרטי, ולכן יוצרים קופונים באמצעות מפעל Coupon.create(code, percent) באובייקט הנלווה. הוא מחזיר null אלא אם הקוד מורכב מ־4 עד 10 תווים של אותיות גדולות וספרות, והאחוז הוא בין 1 ל־90. האובייקט הנלווה גם סופר את הקופונים שיצר באמצעות created, וקוד חיצוני יכול לקרוא את הערך אך לא לשנות אותו. apply(price) מחזירה את המחיר לאחר ההנחה, בעיגול כלפי מטה (price * (100 - percent) / 100).

הקוד שסופק קורא שורות SAVE10 10 200 (קוד, אחוז, מחיר). הוא מדפיס SAVE10: 180 או invalid coupon SAVE10, ולבסוף created: עם המספר.

הקוד שלכם ייכתב ב־Coupon.kt. הקובץ Main.kt מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.

נסו בעצמכם

fun main() {
    // קוד הקלט/פלט שסופק: יש להשאיר אותו כפי שהוא
    val input = generateSequence(::readLine).toList()
    for (line in input) {
        val (code, percent, price) = line.split(" ")
        val coupon = Coupon.create(code, percent.toInt())
        if (coupon == null) println("invalid coupon $code") else println("${coupon.code}: ${coupon.apply(price.toInt())}")
    }
    println("created: ${Coupon.created}")
}
quiz iconבחנו את עצמכם

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

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

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