למה אין ירושה מרובה של מחלקות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 נקי וצפוי, ובו בזמן מאפשרת שימוש חוזר ועוצמתי בקוד באמצעות ממשקים.
אתגר
קלבואו נבנה מערכת של נגן מוזיקה שמדגימה את כלל הירושה היחידה של 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")
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא לתכנות מונחה עצמיםמחלקות לעומת אובייקטיםמילת המפתח thisמתודותשדות (מאפיינים)מתודת הבנאיהעמסת בנאיםחזרה – מחשבון פשוט4ירושה
ירושה בסיסית (extends)מילת המפתח superדריסת מתודות (@Override)שרשור בנאיםהמחלקה Objectירושה יחידה ורב־רמתיתלמה אין ירושה מרובה של מחלקותסיכום – היררכיית עובדים7מתודות מיוחדות ומחלקת Object
מתודת toString()equals() ו-hashCode()מתודת clone()compareTo() ו-Comparableהממשק Comparatorסיכום – מיון מותאם אישית2מצייני גישה ואנקפסולציה
סקירה של רמות הגישהמתודות Getter ו-Setterהסתרת מידעמילת המפתח finalסיכום – מנהל חשבון בנק5פולימורפיזם
יסודות העמסת מתודותדריסת מתודות (בזמן ריצה)המרה כלפי מעלה וכלפי מטההאופרטור instanceofמחלקות ומתודות מופשטותסיכום – מחשבון צורות8מושגי OOP מתקדמים
הרכבה מול ירושהאגרגציה מול הרכבהמחלקות פנימיות, מקוננות ואנונימיותEnums ומתודות של EnumRecords (Java 16+)מחלקות חתומות (Java 17+)3מאפייני מחלקה וחברים סטטיים
משתני מופע לעומת משתנים סטטייםמתודות סטטיותבלוקים סטטייםקבועים (static final)סיכום — מונה ומחלקת עזר6ממשקים ומחלקות מופשטות
מבוא לממשקיםמימוש ממשקיםמימוש של כמה ממשקיםמתודות ברירת מחדל וסטטיות בממשקמחלקות מופשטות לעומת ממשקיםממשקים פונקציונלייםסיכום – מערכת תשלומיםתרגלו בעצמכם: קומפיילר Java אונליין