Menu
Coddy logo textTech

מחלקות מופשטות לעומת ממשקים

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 יחיד)כן (מימושים מרובים)
מצייני גישהכלשהםציבורי בלבד (למתודות מופשטות)

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

challenge icon

אתגר

קל

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

תארגנו את הקוד שלכם בחמישה קבצים:

  • 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() עבור המכונית בלבד (מדגים יכולת של ממשק)
    }
}
quiz iconבחנו את עצמכם

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

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

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