Kotlin Bootcamp for Programmers 4: Object-oriented programming

ה-codelab הזה הוא חלק מקורס Kotlin Bootcamp for Programmers. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. אם יש לכם ידע בנושא, יכול להיות שתוכלו לדלג על חלק מהקטעים. הקורס הזה מיועד למתכנתים שמכירים שפה מונחית-אובייקטים ורוצים ללמוד Kotlin.

מבוא

ב-codelab הזה יוצרים תוכנית Kotlin ולומדים על מחלקות ואובייקטים ב-Kotlin. אם אתם מכירים שפה אחרת מונחית-עצמים, חלק גדול מהתוכן הזה יהיה מוכר לכם, אבל יש הבדלים חשובים בין Kotlin לשפות אחרות, שמטרתם לצמצם את כמות הקוד שאתם צריכים לכתוב. תלמדו גם על מחלקות מופשטות ועל העברת הרשאות לממשק.

במקום ליצור אפליקציה לדוגמה אחת, השיעורים בקורס הזה נועדו להרחיב את הידע שלכם, אבל הם גם עצמאיים למחצה, כך שתוכלו לדלג על חלקים שאתם מכירים. כדי לקשור בין הדוגמאות, רבות מהן מתבססות על נושא האקווריום. אם אתם רוצים לראות את הסיפור המלא של האקווריום, כדאי לעיין בקורס Kotlin Bootcamp for Programmers ב-Udacity.

מה שכדאי לדעת

  • היסודות של Kotlin, כולל סוגים, אופרטורים ולולאות
  • תחביר הפונקציות של Kotlin
  • היסודות של תכנות מונחה-עצמים
  • היסודות של סביבת פיתוח משולבת (IDE) כמו IntelliJ IDEA או Android Studio

מה תלמדו

  • איך יוצרים מחלקות וניגשים למאפיינים ב-Kotlin
  • איך יוצרים ומשתמשים בבוני מחלקות ב-Kotlin
  • איך יוצרים מחלקת משנה ואיך פועל העיקרון של ירושה
  • מידע על מחלקות מופשטות, ממשקים והעברת הרשאה לממשק
  • איך יוצרים סיווגי נתונים ומשתמשים בהם
  • איך משתמשים בסינגלטונים, ב-enums ובמחלקות אטומות

הפעולות שתבצעו:

  • יצירת כיתה עם מאפיינים
  • יצירת בנאי למחלקה
  • יצירת מחלקת משנה
  • לבדוק דוגמאות של מחלקות מופשטות וממשקים
  • יצירת מחלקת נתונים פשוטה
  • מידע על סינגלטונים, סוגי enum ומחלקות sealed

אתם כבר אמורים להכיר את מונחי התכנות הבאים:

  • Classes הם תוכניות לאובייקטים. לדוגמה, מחלקה Aquarium היא תוכנית ליצירת אובייקט של אקווריום.
  • אובייקטים הם מופעים של מחלקות; אובייקט של אקווריום הוא Aquarium בפועל.
  • מאפיינים הם מאפיינים של מחלקות, כמו האורך, הרוחב והגובה של Aquarium.
  • שיטות, שנקראות גם פונקציות חברות, הן הפונקציונליות של המחלקה. השיטות הן הפעולות שאפשר לבצע באובייקט. לדוגמה, אפשר fillWithWater() אובייקט Aquarium.
  • ממשק הוא מפרט שמחלקה יכולה להטמיע. לדוגמה, ניקוי הוא פעולה שמשותפת לאובייקטים שונים, לא רק לאקווריומים, והיא מתבצעת בדרך כלל בצורה דומה לאובייקטים שונים. לכן יכול להיות לכם ממשק בשם Clean שמגדיר שיטה בשם clean(). המחלקה Aquarium יכולה להטמיע את הממשק Clean כדי לנקות את האקווריום עם ספוג רך.
  • חבילות הן דרך לקבץ קוד שקשור אחד לשני כדי לשמור על סדר, או כדי ליצור ספרייה של קוד. אחרי שיוצרים חבילה, אפשר לייבא את התוכן שלה לקובץ אחר ולעשות שימוש חוזר בקוד ובמחלקות שבה.

במשימה הזו, יוצרים חבילה חדשה וכיתה עם כמה מאפיינים ושיטה.

שלב 1: יצירת חבילה

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

  1. בחלונית Project, מתחת לפרויקט Hello Kotlin, לוחצים לחיצה ימנית על התיקייה src.
  2. בוחרים באפשרות חדש > חבילה ונותנים לה את השם example.myapp.

שלב 2: יצירת מחלקה עם מאפיינים

מגדירים מחלקות באמצעות מילת המפתח class, וכמקובל, שמות המחלקות מתחילים באות גדולה.

  1. לוחצים לחיצה ימנית על חבילת example.myapp.
  2. בוחרים באפשרות New > Kotlin File / Class (חדש > קובץ או מחלקה של Kotlin).
  3. בקטע סוג, בוחרים באפשרות כיתה ונותנים שם לכיתה Aquarium. ‫IntelliJ IDEA כולל את שם החבילה בקובץ ויוצר בשבילכם מחלקה ריקה בשם Aquarium.
  4. בתוך המחלקה Aquarium, מגדירים ומאתחלים מאפיינים של var לרוחב, לגובה ולאורך (בסנטימטרים). מאחלים את הנכסים עם ערכי ברירת מחדל.
package example.myapp

class Aquarium {
    var width: Int = 20
    var height: Int = 40
    var length: Int = 100
}

מתחת לפני השטח, Kotlin יוצרת באופן אוטומטי פונקציות getter ו-setter למאפיינים שהגדרתם במחלקה Aquarium, כך שתוכלו לגשת למאפיינים ישירות, למשל myAquarium.length.

שלב 3: יוצרים פונקציה main()‎

יוצרים קובץ חדש בשם main.kt כדי להכיל את הפונקציה main().

  1. בחלונית Project שמימין, לוחצים לחיצה ימנית על החבילה example.myapp.
  2. בוחרים באפשרות New > Kotlin File / Class (חדש > קובץ או מחלקה של Kotlin).
  3. בתפריט הנפתח סוג, משאירים את הבחירה קובץ ונותנים לקובץ את השם main.kt. ‫IntelliJ IDEA כולל את שם החבילה, אבל לא כולל הגדרת מחלקה לקובץ.
  4. מגדירים פונקציה buildAquarium() ובתוכה יוצרים מופע של Aquarium. כדי ליצור מופע, מפנים אל המחלקה כאילו היא פונקציה, Aquarium(). הפעולה הזו קוראת לבנאי של המחלקה ויוצרת מופע של המחלקה Aquarium, בדומה לשימוש ב-new בשפות אחרות.
  5. מגדירים פונקציה main() וקוראים לפונקציה buildAquarium().
package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium()
}

fun main() {
    buildAquarium()
}

שלב 4: מוסיפים שיטה

  1. במחלקת Aquarium, מוסיפים שיטה להדפסת מאפייני המימד של האקווריום.
    fun printSize() {
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }
  1. ב-main.kt, ב-buildAquarium(), מפעילים את השיטה printSize() ב-myAquarium.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
}
  1. מריצים את התוכנית בלחיצה על המשולש הירוק לצד הפונקציה main(). בודקים את התוצאה.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
  1. ב-buildAquarium(), מוסיפים קוד כדי להגדיר את הגובה ל-60 ולהדפיס את מאפייני המאפיין ששונו.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
    myAquarium.height = 60
    myAquarium.printSize()
}
  1. מריצים את התוכנית ומסתכלים על הפלט.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 100 cm Height: 60 cm 

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

שלב 1: יצירת בנאי

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

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

  1. במחלקת Aquarium שיצרתם קודם, משנים את הגדרת המחלקה כך שתכלול שלושה פרמטרים של בנאי עם ערכי ברירת מחדל ל-length, ל-width ול-height, ומקצים אותם למאפיינים המתאימים.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}
  1. ב-Kotlin, הדרך הקומפקטית יותר היא להגדיר את המאפיינים ישירות באמצעות בנאי, באמצעות var או val. בנוסף, Kotlin יוצרת את הפונקציות לקבלת ערכים (getters) ולשינוי ערכים (setters) באופן אוטומטי. אחר כך תוכלו להסיר את הגדרות המאפיינים בגוף הכיתה.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
  1. כשיוצרים אובייקט Aquarium באמצעות בנאי כזה, אפשר לא לציין ארגומנטים ולקבל את ערכי ברירת המחדל, או לציין רק חלק מהם, או לציין את כולם וליצור אובייקט Aquarium בגודל מותאם אישית לחלוטין. בפונקציה buildAquarium(), נסו ליצור אובייקט Aquarium בדרכים שונות באמצעות פרמטרים בעלי שם.
fun buildAquarium() {
    val aquarium1 = Aquarium()
    aquarium1.printSize()
    // default height and length
    val aquarium2 = Aquarium(width = 25)
    aquarium2.printSize()
    // default width
    val aquarium3 = Aquarium(height = 35, length = 110)
    aquarium3.printSize()
    // everything custom
    val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
    aquarium4.printSize()
}
  1. מריצים את התוכנית ומעיינים בפלט.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 25 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 110 cm Height: 35 cm 
Width: 25 cm Length: 110 cm Height: 35 cm 

שימו לב שלא הייתם צריכים להעמיס יתר על המידה על ה-constructor ולכתוב גרסה שונה לכל אחד מהמקרים האלה (ועוד כמה גרסאות לשאר השילובים). ‫Kotlin יוצרת את מה שצריך מתוך ערכי ברירת המחדל והפרמטרים בעלי השם.

שלב 2: מוסיפים בלוקים של init

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

  1. במחלקת Aquarium, מוסיפים בלוק init כדי להדפיס שהאובייקט עובר אתחול, ובלוק שני כדי להדפיס את הנפח בליטרים.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}
  1. מריצים את התוכנית ומעיינים בפלט.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm 
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm 

שימו לב שבלוקי init מבוצעים בסדר שבו הם מופיעים בהגדרת המחלקה, וכולם מבוצעים כשקוראים לבונה.

שלב 3: מידע על בנאים משניים

בשלב הזה נסביר על בנאים משניים ונוסיף אחד לכיתה. בנוסף לקונסטרוקטור ראשי, שיכול לכלול בלוק אחד או יותר של init, מחלקה ב-Kotlin יכולה לכלול גם קונסטרוקטור משני אחד או יותר כדי לאפשר העמסת קונסטרוקטורים, כלומר קונסטרוקטורים עם ארגומנטים שונים.

  1. במחלקת Aquarium, מוסיפים בנאי משני שמקבל מספר דגים כארגומנט, באמצעות מילת המפתח constructor. יוצרים מאפיין של val טנק לנפח המחושב של האקווריום בליטרים על סמך מספר הדגים. נניח שצריך 2 ליטר (2,000 סמ"ק) מים לכל דג, ועוד קצת מקום כדי שהמים לא יישפכו.
constructor(numberOfFish: Int) : this() {
    // 2,000 cm^3 per fish + extra room so water doesn't spill
    val tank = numberOfFish * 2000 * 1.1
}
  1. בתוך ה-constructor המשני, משאירים את האורך והרוחב (שהוגדרו ב-constructor הראשי) ללא שינוי, ומחשבים את הגובה שדרוש כדי שהנפח של המיכל יהיה הנפח הנתון.
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. בפונקציה buildAquarium(), מוסיפים קריאה ליצירת Aquarium באמצעות בנאי המשנה החדש. מדפיסים את הגודל ואת הנפח.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
  1. מריצים את התוכנית ומסתכלים על הפלט.
⇒ aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

שימו לב שהנפח מודפס פעמיים, פעם אחת על ידי הבלוק init בבונה הראשי לפני שהבונה המשני מופעל, ופעם אחת על ידי הקוד ב-buildAquarium().

יכולתם לכלול את מילת המפתח constructor גם בבונה הראשי, אבל ברוב המקרים זה לא נחוץ.

שלב 4: הוספה של פונקציית getter חדשה של מאפיין

בשלב הזה מוסיפים מאחזר מאפיינים מפורש. ‫Kotlin מגדירה באופן אוטומטי getters ו-setters כשמגדירים מאפיינים, אבל לפעמים צריך לשנות או לחשב את הערך של מאפיין. לדוגמה, בדוגמה שלמעלה, הדפסתם את נפח הנתונים של Aquarium. כדי להגדיר את עוצמת הקול כמאפיין, צריך להגדיר בשבילה משתנה ו-getter. מכיוון שצריך לחשב את volume, פונקציית ה-getter צריכה להחזיר את הערך המחושב, ואפשר לעשות זאת באמצעות פונקציה של שורה אחת.

  1. במחלקה Aquarium, מגדירים מאפיין Int בשם volume, ומגדירים שיטה get() לחישוב הנפח בשורה הבאה.
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. מסירים את הבלוק init שמדפיס את עוצמת הקול.
  2. מסירים את הקוד ב-buildAquarium() שמדפיס את עוצמת הקול.
  3. בשיטה printSize(), מוסיפים שורה להדפסת עוצמת הקול.
fun printSize() {
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm "
    )
    // 1 l = 1000 cm^3
    println("Volume: $volume l")
}
  1. מריצים את התוכנית ומסתכלים על הפלט.
⇒ aquarium initializing
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

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

שלב 5: מוסיפים פונקציה להגדרת מאפיין

בשלב הזה יוצרים פונקציה חדשה להגדרת מאפיינים של עוצמת הקול.

  1. במחלקת Aquarium, משנים את volume ל-var כדי שאפשר יהיה להגדיר אותו יותר מפעם אחת.
  2. מוסיפים פונקציית setter למאפיין volume על ידי הוספת שיטת set() מתחת לפונקציית getter, שמחשבת מחדש את הגובה על סמך כמות המים שסופקה. לפי המוסכמה, שם הפרמטר של הפונקציה להגדרת ערך הוא value, אבל אפשר לשנות אותו אם רוצים.
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }
  1. ב-buildAquarium(), מוסיפים קוד כדי להגדיר את נפח האקווריום ל-70 ליטר. מדפיסים את הגודל החדש.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
  1. מריצים שוב את התוכנית ומתבוננים בגובה ובעוצמת הקול ששונו.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm 
Volume: 70 l

עד עכשיו לא היו בקוד משנים של נראות, כמו public או private. הסיבה לכך היא שכל מה שכתוב ב-Kotlin הוא ציבורי כברירת מחדל, כלומר אפשר לגשת לכל דבר בכל מקום, כולל מחלקות, שיטות, מאפיינים ומשתני חברים.

ב-Kotlin, למחלקות, לאובייקטים, לממשקים, לקונסטרוקטורים, לפונקציות, למאפיינים ולפונקציות setter שלהם יכולים להיות משני הרשאות גישה:

  • public – גלוי מחוץ לכיתה. כברירת מחדל, כל דבר הוא ציבורי, כולל משתנים ושיטות של המחלקה.
  • internal, כלומר הוא יהיה גלוי רק בתוך המודול הזה. מודול הוא קבוצה של קובצי Kotlin שעברו קומפילציה יחד, למשל, ספרייה או אפליקציה.
  • private פירושו שהיא תהיה גלויה רק במחלקה הזו (או בקובץ המקור אם עובדים עם פונקציות).
  • protected זהה ל-protected, אבל הוא יהיה גלוי גם לכל תת-המחלקות.private

מידע נוסף זמין במאמר Visibility Modifiers (משני הגדרות החשיפה) במסמכי התיעוד של Kotlin.

משתני חברים

מאפיינים בתוך מחלקה, או משתני חברים, הם public כברירת מחדל. אם מגדירים אותם באמצעות var, הם ניתנים לשינוי, כלומר אפשר לקרוא ולכתוב אותם. אם מגדירים אותם באמצעות val, הם יהיו לקריאה בלבד אחרי האתחול.

אם רוצים שנכס יהיה כזה שהקוד יכול לקרוא או לכתוב בו, אבל קוד חיצוני יכול רק לקרוא בו, אפשר להשאיר את הנכס ואת שיטת ה-getter שלו כציבוריים ולהגדיר את שיטת ה-setter כפרטית, כמו בדוגמה שלמטה.

var volume: Int
    get() = width * height * length / 1000
    private set(value) {
        height = (value * 1000) / (width * length)
    }

במשימה הזו תלמדו איך פועלים מחלקות משנה והורשה ב-Kotlin. הם דומים למה שראיתם בשפות אחרות, אבל יש כמה הבדלים.

ב-Kotlin, כברירת מחדל, אי אפשר ליצור מחלקות משנה. באופן דומה, אי אפשר לבטל את ההגדרה של מאפיינים ומשתני חברים באמצעות מחלקות משנה (אבל אפשר לגשת אליהם).

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

שלב 1: פותחים את הכיתה 'אקווריום'

בשלב הזה, יוצרים את Aquarium המחלקה open, כדי שאפשר יהיה לבטל אותה בשלב הבא.

  1. מסמנים את המחלקה Aquarium ואת כל המאפיינים שלה במילת המפתח open.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }
  1. מוסיפים נכס פתוח shape עם הערך "rectangle".
   open val shape = "rectangle"
  1. מוסיפים נכס פתוח water עם getter שמחזיר 90% מהנפח של Aquarium.
    open var water: Double = 0.0
        get() = volume * 0.9
  1. מוסיפים קוד לשיטה printSize() כדי להדפיס את הצורה ואת כמות המים כאחוז מהנפח.
fun printSize() {
    println(shape)
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm ")
    // 1 l = 1000 cm^3
    println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
  1. ב-buildAquarium(), משנים את הקוד כדי ליצור Aquarium עם width = 25, length = 25 ו-height = 40.
fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
}
  1. מריצים את התוכנית ובודקים את הפלט החדש.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)

שלב 2: יצירת מחלקת משנה

  1. יוצרים מחלקת משנה של Aquarium בשם TowerTank, שמטמיעה מיכל גלילי מעוגל במקום מיכל מלבני. אפשר להוסיף את TowerTank מתחת ל-Aquarium, כי אפשר להוסיף עוד כיתה באותו קובץ כמו הכיתה Aquarium.
  2. ב-TowerTank, מחליפים את המאפיין height שמוגדר בבונה. כדי לבטל את ההגדרה של מאפיין, משתמשים במילת המפתח override במחלקת המשנה.
  1. מגדירים את ה-constructor של TowerTank כך שיקבל diameter. משתמשים ב-diameter גם ל-length וגם ל-width כשקוראים ל-constructor במחלקת העל Aquarium.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
  1. מבטלים את ברירת המחדל של מאפיין הנפח כדי לחשב גליל. הנוסחה לחישוב נפח של צילינדר היא פאי כפול הרדיוס בריבוע כפול הגובה. צריך לייבא את הקבוע PI מ-java.lang.Math.
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }
  1. ב-TowerTank, מחליפים את הנכס water ל-80% מהעוצמה.
override var water = volume * 0.8
  1. שינוי הערך של shape ל-"cylinder".
override val shape = "cylinder"
  1. המחלקת TowerTank הסופית שלכם צריכה להיראות כמו הקוד שבהמשך.

Aquarium.kt:

package example.myapp

import java.lang.Math.PI

... // existing Aquarium class

class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }

    override var water = volume * 0.8
    override val shape = "cylinder"
}
  1. ב-buildAquarium(), תיצור TowerTank בקוטר 25 ס"מ ובגובה 45 ס"מ. מדפיסים את הגודל.

main.kt:

package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium(width = 25, length = 25, height = 40)
    myAquarium.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
  1. מריצים את התוכנית ומסתכלים על הפלט.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
aquarium initializing
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

לפעמים רוצים להגדיר התנהגות או מאפיינים משותפים שיהיו משותפים לכמה מחלקות קשורות. ב-Kotlin יש שתי דרכים לעשות את זה: ממשקים ומחלקות אבסטרקטיות. במשימה הזו יוצרים מחלקה מופשטת AquariumFish למאפיינים שמשותפים לכל הדגים. יוצרים ממשק בשם FishAction כדי להגדיר התנהגות משותפת לכל הדגים.

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

שלב 1. יצירת מחלקה מופשטת

  1. בקטע example.myapp, יוצרים קובץ חדש, AquariumFish.kt.
  2. יוצרים כיתה, שנקראת גם AquariumFish, ומסמנים אותה ב-abstract.
  3. מוסיפים מאפיין String אחד, color, ומסמנים אותו באמצעות abstract.
package example.myapp

abstract class AquariumFish {
    abstract val color: String
}
  1. יוצרים שתי מחלקות משנה של AquariumFish, ‏Shark ו-Plecostomus.
  2. מכיוון ש-color הוא מופשט, מחלקות המשנה חייבות ליישם אותו. הופכים את Shark לאפור ואת Plecostomus לזהב.
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. ב-main.kt, יוצרים פונקציה makeFish() כדי לבדוק את הכיתות. יוצרים מופע של Shark ושל Plecostomus, ואז מדפיסים את הצבע של כל אחד מהם.
  2. מוחקים את קוד הבדיקה הקודם ב-main() ומוסיפים קריאה ל-makeFish(). הקוד שלכם צריך להיראות בערך כמו הקוד שבהמשך.

main.kt:

package example.myapp

fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()

    println("Shark: ${shark.color}")
    println("Plecostomus: ${pleco.color}")
}

fun main () {
    makeFish()
}
  1. מריצים את התוכנית ומסתכלים על הפלט.
⇒ Shark: gray 
Plecostomus: gold

התרשים הבא מייצג את המחלקה Shark ואת המחלקה Plecostomus, שהן מחלקות משנה של המחלקה המופשטת AquariumFish.

תרשים שמציג את המחלקה המופשטת AquariumFish ושתי מחלקות משנה, Shark ו-Plecostumus.

שלב 2. יצירת ממשק

  1. ב-AquariumFish.kt, יוצרים ממשק בשם FishAction עם method בשם eat().
interface FishAction  {
    fun eat()
}
  1. מוסיפים FishAction לכל אחת ממחלקות המשנה, ומטמיעים את eat() כך שהיא תדפיס את הפעולות של הדג.
class Shark: AquariumFish(), FishAction {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus: AquariumFish(), FishAction {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. בפונקציה makeFish(), כל דג שיצרתם אוכל משהו באמצעות קריאה לפונקציה eat().
fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()
    println("Shark: ${shark.color}")
    shark.eat()
    println("Plecostomus: ${pleco.color}")
    pleco.eat()
}
  1. מריצים את התוכנית ומסתכלים על הפלט.
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

בתרשים הבא מוצגות המחלקה Shark והמחלקה Plecostomus, ששתיהן מורכבות מהממשק FishAction ומטמיעות אותו.

מתי כדאי להשתמש במחלקות מופשטות ומתי בממשקים

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

כמו שצוין למעלה, למחלקות אבסטרקטיות יכולים להיות constructors, אבל לממשקים לא יכולים להיות, אבל חוץ מזה הם דומים מאוד. אז מתי כדאי להשתמש בכל אחת מהן?

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

קומפוזיציה מובילה לרוב להצפנה טובה יותר, לצימוד (תלות הדדית) נמוך יותר, לממשקים נקיים יותר ולקוד שקל יותר להשתמש בו. לכן, השימוש בקומפוזיציה עם ממשקים הוא העיצוב המועדף. מצד שני, ירושה ממחלקה מופשטת מתאימה באופן טבעי לפתרון בעיות מסוימות. לכן, מומלץ להשתמש בהרכבה, אבל אם יש מקרים שבהם עדיף להשתמש בירושה, אפשר לעשות את זה גם ב-Kotlin.

  • כדאי להשתמש בממשק אם יש הרבה שיטות והטמעה אחת או שתיים שמוגדרות כברירת מחדל, כמו בדוגמה AquariumAction שבהמשך.
interface AquariumAction {
    fun eat()
    fun jump()
    fun clean()
    fun catchFish()
    fun swim()  {
        println("swim")
    }
}
  • משתמשים במחלקה מופשטת בכל פעם שלא ניתן להשלים מחלקה. לדוגמה, אם נחזור לכיתה AquariumFish, אפשר לגרום לכל AquariumFish להטמיע את FishAction, ולספק הטמעה שמוגדרת כברירת מחדל עבור eat, תוך השארת color מופשטת, כי אין באמת צבע ברירת מחדל לדגים.
interface FishAction  {
    fun eat()
}

abstract class AquariumFish: FishAction {
   abstract val color: String
   override fun eat() = println("yum")
}

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

במשימה הזו, אתם משתמשים בהעברת הרשאות לממשק כדי להוסיף פונקציונליות לכיתה.

שלב 1: יוצרים ממשק חדש

  1. ב-AquariumFish.kt, מסירים את המחלקה AquariumFish. במקום לרשת מהמחלקה AquariumFish, המחלקות Plecostomus ו-Shark יטמיעו ממשקים גם לפעולת הדג וגם לצבע שלו.
  2. יוצרים ממשק חדש, FishColor, שמגדיר את הצבע כמחרוזת.
interface FishColor {
    val color: String
}
  1. משנים את Plecostomus כדי להטמיע שני ממשקים, FishAction ו-FishColor. צריך לשנות את הערך של color מ-FishColor ואת הערך של eat() מ-FishAction.
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. משנים את המחלקה Shark כך שהיא תטמיע גם את שני הממשקים, FishAction ו-FishColor, במקום לרשת מ-AquariumFish.
class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. הקוד הסופי אמור להיראות כך:
package example.myapp

interface FishAction {
    fun eat()
}

interface FishColor {
    val color: String
}

class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}

class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

שלב 2: יוצרים מחלקה יחידה

לאחר מכן מטמיעים את ההגדרה של חלק ההרשאה על ידי יצירת מחלקה מסייעת שמטמיעה את FishColor. יוצרים מחלקה בסיסית בשם GoldColor שמטמיעה את FishColor – כל מה שהיא עושה זה להצהיר שהצבע שלה הוא זהב.

אין טעם ליצור כמה מופעים של GoldColor, כי כולם יעשו בדיוק את אותו הדבר. לכן, ב-Kotlin אפשר להצהיר על מחלקה שניתן ליצור ממנה רק מופע אחד באמצעות מילת המפתח object במקום class. ‫Kotlin תיצור את המופע הזה, והמופע הזה יקבל הפניה משם המחלקה. אחרי זה כל האובייקטים האחרים יכולים להשתמש רק במופע הזה – אין אפשרות ליצור מופעים אחרים של המחלקה הזו. אם אתם מכירים את תבנית העיצוב Singleton, כך מטמיעים Singleton ב-Kotlin.

  1. ב-AquariumFish.kt, יוצרים אובייקט בשם GoldColor. שינוי הצבע מברירת המחדל.
object GoldColor : FishColor {
   override val color = "gold"
}

שלב 3: הוספת העברת הרשאות לממשק של FishColor

עכשיו אפשר להשתמש בהענקת גישה לממשק.

  1. ב-AquariumFish.kt, מסירים את ההחלפה של color מ-Plecostomus.
  2. משנים את המחלקה Plecostomus כדי לקבל את הצבע שלה מ-GoldColor. כדי לעשות זאת, מוסיפים by GoldColor להצהרת המחלקה ויוצרים את ההעברה. המשמעות היא שבמקום להטמיע את FishColor, צריך להשתמש בהטמעה שסופקה על ידי GoldColor. לכן, בכל פעם שמתבצעת גישה אל color, הגישה מועברת אל GoldColor.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

בכיתה כמו שהיא, כל הפלקו יהיו בצבע זהב, אבל הדגים האלה מגיעים למעשה בהרבה צבעים. כדי לפתור את הבעיה, אפשר להוסיף פרמטר של בנאי לצבע עם GoldColor כצבע ברירת המחדל של Plecostomus.

  1. משנים את המחלקה Plecostomus כך שתקבל את fishColor עם הבונה שלה, ומגדירים את ברירת המחדל שלה ל-GoldColor. שינוי ההרשאה מ-by GoldColor ל-by fishColor.
class Plecostomus(fishColor: FishColor = GoldColor):  FishAction,
       FishColor by fishColor {
   override fun eat() {
       println("eat algae")
   }
}

שלב 4: הוספת העברת הרשאות לממשק עבור FishAction

באותו אופן, אפשר להשתמש בהענקת הרשאה לממשק עבור FishAction.

  1. ב-AquariumFish.kt יוצרים מחלקה PrintingFishAction שמטמיעה את FishAction, שמקבלת String, food, ואז מדפיסה את מה שהדג אוכל.
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. במחלקת Plecostomus, מסירים את פונקציית ההחלפה eat(), כי תחליפו אותה בהענקת הרשאה.
  2. בהצהרה של Plecostomus, מעבירים את FishAction אל PrintingFishAction, ומעבירים את "eat algae".
  3. בגלל כל ההענקות האלה של הרשאות, אין קוד בגוף של המחלקה Plecostomus, ולכן צריך להסיר את {}, כי כל ההגדרות שמוגדרות מחדש מטופלות על ידי הענקת הרשאות לממשק
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

בתרשים הבא מוצגים המחלקות Shark ו-Plecostomus, ששתיהן מורכבות מהממשקים PrintingFishAction ו-FishColor, אבל מייצגות את ההטמעה שלהן.

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

מחלקת נתונים דומה ל-struct בשפות אחרות – היא קיימת בעיקר כדי להכיל נתונים מסוימים – אבל אובייקט של מחלקת נתונים הוא עדיין אובייקט. לאובייקטים של מחלקות נתונים ב-Kotlin יש כמה יתרונות נוספים, כמו כלי עזר להדפסה ולהעתקה. במשימה הזו תיצרו מחלקת נתונים פשוטה ותלמדו על התמיכה ש-Kotlin מספקת למחלקות נתונים.

שלב 1: יוצרים סיווג נתונים

  1. מוסיפים חבילה חדשה decor מתחת לחבילה example.myapp כדי להכיל את הקוד החדש. לוחצים לחיצה ימנית על example.myapp בחלונית Project ובוחרים באפשרות File > New > Package (קובץ > חדש > חבילה).
  2. בחבילה, יוצרים מחלקה חדשה בשם Decoration.
package example.myapp.decor

class Decoration {
}
  1. כדי להפוך את Decoration למחלקת נתונים, מוסיפים את מילת המפתח data לפני הצהרת המחלקה.
  2. מוסיפים מאפיין String בשם rocks כדי לספק לכיתה נתונים.
data class Decoration(val rocks: String) {
}
  1. בקובץ, מחוץ לכיתה, מוסיפים פונקציה makeDecorations() כדי ליצור ולהדפיס מופע של Decoration עם "granite".
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. מוסיפים פונקציה main() כדי לקרוא ל-makeDecorations(), ומריצים את התוכנית. שימו לב לפלט הסביר שנוצר כי מדובר במחלקת נתונים.
⇒ Decoration(rocks=granite)
  1. ב-makeDecorations(), יוצרים שני אובייקטים נוספים של Decoration ששניהם בצבע 'אפור צפחה' ומדפיסים אותם.
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)
}
  1. ב-makeDecorations(), מוסיפים הצהרת הדפסה שמדפיסה את התוצאה של השוואה בין decoration1 לבין decoration2, ועוד הצהרה שמשווה בין decoration3 לבין decoration2. משתמשים בשיטה equals()‎ שמופיעה במחלקות נתונים.
    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
  1. מריצים את הקוד.
⇒ Decoration(rocks=granite)
Decoration(rocks=slate)
Decoration(rocks=slate)
false
true

שלב 2. שימוש בפירוק מבנה

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

val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver

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

val (rock, wood, diver) = decoration

הפעולה הזו נקראת פירוק מבנה והיא קיצור דרך שימושי. מספר המשתנים צריך להיות זהה למספר המאפיינים, והמשתנים מוקצים בסדר שבו הם מוצהרים במחלקה. הנה דוגמה מלאה שאפשר לנסות בקובץ Decoration.kt.

// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver)
crystal
wood
diver

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

    val (rock, _, diver) = d5

במשימה הזו תלמדו על כמה מחלקות מיוחדות ב-Kotlin, כולל:

  • מחלקות Singleton
  • טיפוסים בני מנייה (enum)
  • מחלקות אטומות

שלב 1: נזכרים בכיתות סינגלטון

נזכרים בדוגמה הקודמת עם המחלקה GoldColor.

object GoldColor : FishColor {
   override val color = "gold"
}

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

שלב 2: יצירת enum

‫Kotlin תומכת גם ב-enums, שמאפשרים לכם למנות משהו ולהתייחס אליו לפי שם, בדומה לשפות אחרות. מצהירים על enum על ידי הוספת מילת המפתח enum לפני ההצהרה. הצהרה בסיסית של enum צריכה לכלול רק רשימה של שמות, אבל אפשר גם להגדיר שדה אחד או יותר שמשויכים לכל שם.

  1. ב-Decoration.kt, אפשר לנסות דוגמה של enum.
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

סוגי enum דומים קצת לסינגלטונים – יכול להיות רק אחד, ורק אחד מכל ערך בספירה. לדוגמה, יכול להיות רק רכיב Color.RED אחד, רכיב Color.GREEN אחד ורכיב Color.BLUE אחד. בדוגמה הזו, ערכי ה-RGB מוקצים למאפיין rgb כדי לייצג את רכיבי הצבע. אפשר גם לקבל את הערך הסידורי של enum באמצעות המאפיין ordinal ואת השם שלו באמצעות המאפיין name.

  1. ננסה דוגמה נוספת של enum.
enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
⇒ EAST
2
90

שלב 3: יוצרים מחלקה אטומה

מחלקה חתומה היא מחלקה שאפשר ליצור ממנה מחלקת משנה, אבל רק בתוך הקובץ שבו היא מוצהרת. אם מנסים ליצור מחלקת משנה של המחלקה בקובץ אחר, מוצגת שגיאה.

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

  1. ב-AquariumFish.kt, אפשר לנסות דוגמה של מחלקה אטומה, בהתאם לנושא המים.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()

fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

אי אפשר ליצור מחלקת משנה (subclass) של המחלקה Seal בקובץ אחר. אם רוצים להוסיף עוד סוגים של Seal, צריך להוסיף אותם באותו קובץ. כך מחלקות חתומות הן דרך בטוחה לייצג מספר קבוע של סוגים. לדוגמה, מחלקות אטומות מצוינות להחזרת הצלחה או שגיאה מ-API של רשת.

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

מחלקות ובוני אובייקטים

  • מגדירים מחלקה ב-Kotlin באמצעות class.
  • ‫Kotlin יוצרת באופן אוטומטי פונקציות setter ו-getter למאפיינים.
  • מגדירים את ה-constructor הראשי ישירות בהגדרת המחלקה. לדוגמה:
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • אם קונסטרוקטור ראשי צריך קוד נוסף, כותבים אותו באחד או יותר מinit בלוקים.
  • אפשר להגדיר מחלקה עם בנאי משני אחד או יותר באמצעות constructor, אבל בסגנון Kotlin מקובל יותר להשתמש בפונקציית factory.

משנים של חשיפה ותת-מחלקות

  • כל המחלקות והפונקציות ב-Kotlin הן public כברירת מחדל, אבל אפשר להשתמש במגדירי גישה כדי לשנות את הגישה ל-internal,‏ private או protected.
  • כדי ליצור מחלקת משנה, צריך לסמן את מחלקת האב בתג open.
  • כדי לשנות את ההתנהגות של methods ומאפיינים במחלקת משנה, צריך לסמן את ה-methods והמאפיינים ב-open במחלקת האב.
  • אפשר ליצור מחלקה משנית ממחלקה חתומה רק באותו קובץ שבו היא מוגדרת. כדי ליצור מחלקה חתומה, מוסיפים את הקידומת sealed להצהרה.

מחלקות נתונים, סינגלטונים וסוגי ספירה

  • כדי ליצור סיווג נתונים, מוסיפים את הקידומת data להצהרה.
  • Destructuring היא דרך מקוצרת להקצאת המאפיינים של אובייקט data למשתנים נפרדים.
  • כדי ליצור מחלקת סינגלטון, משתמשים ב-object במקום ב-class.
  • הגדרת enum באמצעות enum class.

מחלקות מופשטות, ממשקים והעברה

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

תיעוד של Kotlin

אם אתם רוצים לקבל מידע נוסף על נושא כלשהו בקורס הזה, או אם נתקעתם, https://kotlinlang.org הוא המקום הכי טוב להתחיל בו.

מדריכים ל-Kotlin

באתר https://try.kotlinlang.org יש הדרכות מפורטות שנקראות Kotlin Koans, מפרש מבוסס-אינטרנט וסט מלא של מסמכי עזר עם דוגמאות.

קורס של Udacity

כדי לצפות בקורס של Udacity בנושא הזה, אפשר לעבור אל Kotlin Bootcamp for Programmers.

IntelliJ IDEA

מסמכי התיעוד של IntelliJ IDEA זמינים באתר JetBrains.

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

  • אם צריך, מקצים שיעורי בית.
  • להסביר לתלמידים איך להגיש מטלות.
  • בודקים את שיעורי הבית.

אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.

אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.

עונים על השאלות הבאות

שאלה 1

לכיתות יש שיטה מיוחדת שמשמשת כתוכנית ליצירת אובייקטים של אותה כיתה. מה שם השיטה?

‫▢ בנאי

‫▢ יוצר מופעים

‫▢ יצרן

▢ שרטוט

שאלה 2

איזו מההצהרות הבאות לגבי ממשקים ומחלקות אבסטרקטיות היא לא נכונה?

‫▢ מחלקות מופשטות יכולות לכלול בנאים.

‫▢ לממשקים לא יכולים להיות בנאים.

‫▢ אפשר ליצור מופעים של ממשקים ומחלקות מופשטות באופן ישיר.

‫▢ מחלקות משנה של המחלקה המופשטת חייבות להטמיע מאפיינים מופשטים.

שאלה 3

איזה מהבאים הוא לא משנה נראות ב-Kotlin למאפיינים, לשיטות וכו'?

internal

nosubclass

protected

private

שאלה 4

נתונה מחלקת הנתונים הבאה:
data class Fish(val name: String, val species:String, val colors:String)
איזה מהקודים הבאים לא תקף ליצירה ולפירוק של אובייקט Fish?

val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")

val (name2, _, colors2) = Fish("Bitey", "shark", "gray")

val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")

val (name4, species4, colors4) = Fish("Harry", "halibut")

שאלה 5

נניח שאתם הבעלים של גן חיות עם הרבה בעלי חיים שצריך לטפל בהם. מה מהפעולות הבאות לא נכלל בהטמעה של טיפול ותחזוקה?

‫▢ interface לסוגים שונים של מזונות שבעלי חיים אוכלים.

‫▢ abstract Caretaker כיתה שבה אפשר ליצור סוגים שונים של מטפלים.

‫▢ interface על מתן מים נקיים לבעל חיים.

‫▢ כיתה data לרשומה בלוח הזמנים של האכלה.

עוברים לשיעור הבא: 5.1 תוספים

סקירה כללית של הקורס, כולל קישורים ל-Codelabs אחרים, זמינה במאמר "Kotlin Bootcamp for Programmers: Welcome to the course".