Menu
Coddy logo textTech

למה אין ירושה מרובה של מחלקות

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 26 מתוך 87.

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

דמיינו אילו Java אפשרה ירושה מרובה. שקלו את התרחיש ההיפותטי הבא:

// הקוד הזה אינו תקין ב-Java
public class A {
    public void greet() {
        System.out.println("Hello from A");
    }
}

public class B extends A {
    @Override
    public void greet() {
        System.out.println("Hello from B");
    }
}

public class C extends A {
    @Override
    public void greet() {
        System.out.println("Hello from C");
    }
}

// אסור ב-Java!
public class D extends B, C { }

אם קראת ל־greet() על אובייקט D, איזו גרסה אמורה לפעול? זו של B או של C? העמימות הזאת היא בעיית היהלום, שנקראת כך על שם צורת היהלום שיוצרת תרשים הירושה.

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

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

// זה אכן תקין ב-Java
public class Dog extends Animal implements Runnable, Comparable<Dog> {
    // אפשר לממש כמה ממשקים
}

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

challenge icon

אתגר

קל

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

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

תיצרו ארבעה קבצים כדי לארגן את הקוד:

  • AudioDevice.java: צרו את מחלקת הבסיס המשותפת לכל המכשירים שמשמיעים שמע. היא צריכה לכלול:
    • שדה פרטי עבור deviceName (String)
    • בנאי שמקבל את שם המכשיר
    • מתודה getDeviceName() שמחזירה את השם
    • מתודה playSound() שמדפיסה: [deviceName]: Playing audio
  • MusicPlayer.java: צרו מחלקה שיורשת מ-AudioDevice ומוסיפה תכונות ייחודיות למוזיקה:
    • שדה פרטי עבור currentSong (String)
    • בנאי שמקבל את שם המכשיר ומשתמש ב-super(deviceName)
    • מתודה setSong(String song) שמגדירה את השיר הנוכחי
    • דרסו את playSound() כך שתדפיס: [deviceName]: Now playing - [currentSong]
  • SmartSpeaker.java: צרו מחלקה שיורשת מ-MusicPlayer (ולא גם מ-MusicPlayer וגם מאיזושהי מחלקת VoiceAssistant — זו תהיה ירושה מרובה!). הרמקול החכם מתבסס על נגן המוזיקה ומוסיף יכולות קוליות:
    • שדה פרטי עבור assistantName (String)
    • בנאי שמקבל את שם המכשיר ואת שם העוזר, ומשתמש ב-super(deviceName)
    • מתודה voiceCommand(String command) שמדפיסה: [assistantName]: Processing "[command]"
    • מתודה getFullInfo() שמדפיסה: [deviceName] with [assistantName] assistant
  • Main.java: הדגימו את שרשרת הירושה הרב־שכבתית שלכם. תקבלו שלוש קלטים: שם של מכשיר (String), שם של עוזר (String) ושם של שיר (String). צרו SmartSpeaker, הגדירו שיר ואז קראו למתודות האלה לפי הסדר:
    • getFullInfo()
    • playSound()
    • voiceCommand("skip to next")

תקבלו שלוש קלטים: שם המכשיר, שם העוזר ושם השיר.

שימו לב איך SmartSpeaker מקבלת יכולות מוזיקה באמצעות ירושה מ-MusicPlayer בשרשרת (AudioDevice -> MusicPlayer -> SmartSpeaker), במקום לנסות לרשת מכמה מחלקות לא קשורות. כך Java שומרת על ירושה מסודרת וחד-משמעית!

נסו בעצמכם

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // קרא את הקלטים
        String deviceName = scanner.nextLine();
        String assistantName = scanner.nextLine();
        String songName = scanner.nextLine();
        
        // TODO: צור SmartSpeaker עם deviceName ו-assistantName
        
        // TODO: הגדר את השיר באמצעות setSong()
        
        // TODO: קרא ל-getFullInfo()
        
        // TODO: קרא ל-playSound()
        
        // TODO: קרא ל-voiceCommand("skip to next")
    }
}
quiz iconבחנו את עצמכם

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

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

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