מה זו מחלקה אבסטרקטית
ממשק מצהיר על התנהגות בלי מצב. מחלקה רגילה ממומשת במלואה ואפשר ליצור ממנה מופע. מחלקה אבסטרקטית נמצאת באמצע: היא יכולה להכיל שדות, בנאים ומתודות גמורות כמו מחלקה רגילה, אבל היא גם יכולה להשאיר חלק מהמתודות לא ממומשות, ואסור ליצור ממנה מופע ישירות. מסמנים אותה במילת המפתח abstract.
הרעיון הוא לרכז במקום אחד את כל מה שמשותף לתתי-המחלקות, ולחייב כל תת-מחלקה להשלים את החלקים שבאמת שונים.
Animal מגדירה את getName() פעם אחת לכל תתי-המחלקות ומצהירה על sound() כ-abstract: מתודה עם חתימה אבל בלי גוף, ש-Dog חייבת לספק.
אי אפשר ליצור מופע של מחלקה אבסטרקטית
מכיוון שלמחלקה אבסטרקטית עשויות להיות מתודות לא גמורות, יצירה ישירה שלה הייתה משאירה אתכם עם אובייקט לא שלם. הקומפיילר דוחה את זה:
Animal a = new Animal("???"); // error: Animal is abstract; cannot be instantiated
תמיד יוצרים מופע של תת-מחלקה קונקרטית, כזו שמימשה כל מתודה אבסטרקטית. אחר כך אפשר להחזיק את המופע הזה במשתנה מהטיפוס האבסטרקטי, וזו בדיוק הדרך להשתמש בהפשטה.
מתודות אבסטרקטיות מחייבות את תתי-המחלקות להחליט
מתודה abstract היא הבטחה שתת-המחלקה חייבת לקיים. אם תת-מחלקה שוכחת לממש אחת כזו, תת-המחלקה עצמה הופכת לאבסטרקטית והקומפיילר מודיע לכם על כך. זה המנוף העיקרי של מחלקה אבסטרקטית: היא מבטיחה שהתנהגות מסוימת קיימת, בלי להכתיב מה היא עושה.
describe() נכתבת פעם אחת ב-Shape, ובכל זאת קוראת ל-area() של כל תת-מחלקה. המחלקה האבסטרקטית מספקת את השלד המשותף; תתי-המחלקות מספקות את הפרטים.
מצב משותף ובנאים
בניגוד לממשק מסורתי, מחלקה אבסטרקטית יכולה להחזיק שדות מופע ולהגדיר בנאים. הבנאי אף פעם לא יוצר Animal או Shape בעצמו: הוא רץ דרך super(...) כשנוצרת תת-מחלקה, ומאתחל את המצב המשותף.
השדה balance, המתודה deposit והמתודה applyInterest נמצאים במקום אחד. רק המדיניות שבאמת משתנה, interestRate(), נשארת אבסטרקטית. הבנאי של תת-מחלקה חייב לקרוא ל-super(...) כדי לאתחל את המצב שעבר בירושה.
מלכודת: שילוב של abstract ו-final
abstract ו-final הם הפכים. מתודה abstract דורשת דריסה; מתודה final אוסרת אותה. סימון של אותו דבר בשתי הדרכים, או מחלקה אבסטרקטית שהיא גם final, הוא שגיאת קומפילציה. זכרו גם שלמחלקה אבסטרקטית יכולות להיות אפס מתודות אבסטרקטיות: הצהרה על מחלקה כ-abstract רק כדי למנוע יצירת מופע שלה היא חוקית, ולפעמים שימושית לטיפוסי בסיס שאתם רוצים רק להרחיב.
abstract final class Bad { } // error: abstract and final conflict
abstract class Base {
abstract final void f(); // error: an abstract method can't be final
}
מחלקה אבסטרקטית מול ממשק
יש ביניהם חפיפה, ולכן הבחירה תלויה במה שצריך לשתף:
- מחלקה אבסטרקטית: השתמשו בה למחלקות קרובות שחולקות מצב וקוד. גם
SavingsוגםCheckingמרחיבות אתAccount, ויורשות את השדהbalanceואת הלוגיקה שלdeposit. מחלקה מרחיבה רק מחלקה אחת. - ממשק: השתמשו בו ליכולת שמחלקות לא קשורות יכולות לחלוק.
Birdו-Airplaneיכולות להיות שתיהןFlyableבלי לחלוק שום מימוש. מחלקה יכולה לממש הרבה ממשקים.
דפוס נפוץ משלב את שניהם: ממשק מגדיר את החוזה, ומחלקה אבסטרקטית מממשת את הקוד החוזר כך שתתי-המחלקות הקונקרטיות משלימות רק את מה שייחודי להן.
הבא בתור: פולימורפיזם
שימו לב שבכל הדוגמאות הקודמות החזקנו מופע של תת-מחלקה במשתנה מהטיפוס האבסטרקטי, קראנו למתודה וקיבלנו אוטומטית את ההתנהגות של תת-המחלקה. היכולת הזו, טיפוס הפניה אחד והרבה התנהגויות בזמן ריצה, היא פולימורפיזם, והיא מה שהופך מחלקות אבסטרקטיות וממשקים לכדאיים. זה הנושא של העמוד הבא.
שאלות נפוצות
מה זו מחלקה אבסטרקטית ב-Java?
מחלקה אבסטרקטית היא מחלקה שהוצהרה עם מילת המפתח abstract ואי אפשר ליצור ממנה מופע בפני עצמה. היא נועדה להרחבה. היא יכולה לשלב מתודות ושדות ממומשים במלואם (מצב וקוד משותפים לתתי-המחלקות) עם מתודות abstract בלי גוף: את אלה כל תת-מחלקה אחראית לממש.
האם אפשר ליצור מופע של מחלקה אבסטרקטית ב-Java?
לא. new AbstractType() היא שגיאת קומפילציה, כי למחלקה אבסטרקטית עשויות להיות מתודות לא ממומשות (abstract), כך שהאובייקט היה לא שלם. יוצרים מופע של תת-מחלקה קונקרטית שממלאת את כל המתודות האבסטרקטיות, ואז שומרים אותו במשתנה מהטיפוס האבסטרקטי.
מה ההבדל בין מחלקה אבסטרקטית לממשק ב-Java?
למחלקה אבסטרקטית יכולים להיות שדות מופע, בנאים ולוגיקה ממומשת חלקית, אבל מחלקה יכולה להרחיב רק אחת כזו. ממשק מצהיר על התנהגות בלי מצב מופע, ומחלקה יכולה לממש הרבה ממשקים. השתמשו במחלקה אבסטרקטית כדי לשתף מצב וקוד בין תתי-מחלקות קרובות; השתמשו בממשק כדי לתת למחלקות לא קשורות יכולת משותפת.