מחלקות מופשטות לעומת ממשקים
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 38 מתוך 87.
עכשיו, כשאתה מבין גם מחלקות מופשטות וגם ממשקים, עולה שאלה נפוצה: מתי כדאי להשתמש באחד ולא באחר? הבחירה תלויה במה שאתה מנסה למדל.
השתמשו במחלקה מופשטת כאשר מחלקות חולקות בסיס משותף עם מצב או התנהגות משותפים. למחלקות מופשטות יכולים להיות משתני מופע, בנאים ושילוב של מתודות מופשטות וממומשות:
public abstract class Animal {
protected String name; // מצב משותף
public Animal(String name) {
this.name = name;
}
public void sleep() { // התנהגות משותפת
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // חובה לממש
}השתמשו ב־ממשק כשרוצים להגדיר יכולת שמחלקות שאינן קשורות זו לזו יכולות לשתף. ממשקים מתמקדים במה שאובייקט יכול לעשות, ולא במה שהוא:
public interface Flyable {
void fly();
}
// מחלקות שאינן קשורות זו לזו יכולות לחלוק את היכולת הזו
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }הנה השוואה מהירה:
| מאפיין | מחלקה מופשטת | ממשק |
|---|---|---|
| משתני מופע | כן | קבועים בלבד |
| בנאים | כן | לא |
| ירושה מרובה | לא (extends יחיד) | כן (מימושים מרובים) |
| מצייני גישה | כלשהם | ציבורי בלבד (למתודות מופשטות) |
הנחיה מעשית: אם מצאת את עצמך יוצר מחלקה מופשטת שיש בה רק מתודות מופשטות וללא מצב, סביר להניח שממשק יהיה הבחירה הטובה יותר.
אתגר
קלבואו נבנה מערכת כלי רכב שמדגימה מתי להשתמש במחלקות מופשטות לעומת ממשקים. תדגמו כלי רכב שחולקים מצב והתנהגות משותפים באמצעות מחלקה מופשטת, ובמקביל תוסיפו יכולות אופציונליות באמצעות ממשקים.
תארגנו את הקוד שלכם בחמישה קבצים:
Vehicle.java: צרו מחלקה מופשטת שתשמש בסיס לכל כלי הרכב. לכל כלי רכב יש שדהbrand(String) ושדהyear(int). הוסיפו בנאי שיאתחל את שני השדות, מתודות getter לכל אחד מהם, ומתודה מופשטתstartEngine()שמחזירה String. הוסיפו גם מתודה ממומשתgetInfo()שמחזירה:[brand] ([year]). זהו מקרה שימוש מושלם למחלקה מופשטת: כלי הרכב חולקים מצב (brand, year) והתנהגות מסוימת (getInfo), אבל כל אחד מתניע את המנוע שלו בצורה שונה.Convertible.java: הגדירו ממשק לכלי רכב שיכולים לפתוח ולסגור את הגג. היכולת הזו אינה קשורה למה שכלי רכב הוא: זה משהו שכלי רכב מסוימים יכולים לעשות. הכריזו על שתי מתודות:openRoof()ו-closeRoof(), ששתיהן מחזירות String.Car.java: צרו מחלקה שמרחיבה אתVehicleומממשת אתConvertible. למחלקה Car יש שדה נוסף בשםnumDoors(int). השתמשו ב-superכדי לאתחל את השדות שעברו בירושה. ממשו אתstartEngine()כך שתחזיר:[brand] car engine started. ממשו אתopenRoof()כך שתחזיר:[brand] roof opening, ואתcloseRoof()כך שתחזיר:[brand] roof closing.Motorcycle.java: צרו מחלקה שמרחיבה אתVehicleאך אינה מממשת אתConvertible: לאופנועים אין גגות! למחלקה Motorcycle יש שדהhasSidecar(boolean). ממשו אתstartEngine()כך שתחזיר:[brand] motorcycle engine roaring.Main.java: חברו את כל החלקים כדי להציג את ההבדל בין מחלקות מופשטות לממשקים. תקבלו ארבעה קלטים: מותג המכונית, שנת המכונית, מותג האופנוע ושנת האופנוע.צרו Car (עם 4 דלתות) ו-Motorcycle (ללא סירה צידית). תחילה, הדגימו את ההתנהגות המשותפת של המחלקה המופשטת על ידי הדפסת
getInfo()עבור שני כלי הרכב. שניהם יורשים את המתודה הזו מ-Vehicle. לאחר מכן, הדפיסו את התוצאה שלstartEngine()עבור כל אחד מהם: שימו לב שכל סוג כלי רכב מממש את המתודה באופן שונה.לבסוף, הדגימו את יכולת הממשק: מאחר שרק Car מממשת את
Convertible, קראו והדפיסו אתopenRoof()ואחריה אתcloseRoof()עבור המכונית בלבד.
תקבלו ארבעה קלטים: מותג המכונית (String), שנת המכונית (int), מותג האופנוע (String) ושנת האופנוע (int).
הפלט שלכם צריך להכיל שש שורות בסך הכול. שימו לב ששני כלי הרכב חולקים את המצב של המחלקה המופשטת ואת המתודה getInfo(), אבל רק למכונית יש את היכולת לפתוח ולסגור את הגג. כך אפשר לראות מתי כדאי להשתמש בכל אחת מהגישות!
נסו בעצמכם
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// קרא את נתוני הקלט
String carBrand = scanner.nextLine();
int carYear = scanner.nextInt();
scanner.nextLine(); // צרוך את תו השורה החדשה
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO: צור Car עם 4 דלתות
// TODO: צור Motorcycle ללא סירה צדדית (false)
// TODO: הדפס את getInfo() עבור שני כלי הרכב (מדגים התנהגות משותפת של מחלקה מופשטת)
// TODO: הדפס את startEngine() עבור שני כלי הרכב (מדגים מימושים שונים)
// TODO: הדפס את openRoof() ואת closeRoof() עבור המכונית בלבד (מדגים יכולת של ממשק)
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין