ראשי
פרק 1
פרק 2
פרק 3
פרק 4
פרק 5
פרק 6
פרק 7
פרק 8

פרק 4 - תורשה ופולימורפיזם


תוכן הפרק:

מהי תורשה
כללי תורשה
פולימורפיזם
מניעת ירושה
פולימורפיזם מופשט
ממשקים
האופרטור instanceof



מהי תורשה ולמה היא טובה?

Java, כמו שפות אחרות, מאפשרת לנו להגדיר מחלקות ראשיות (הורה) ומחלקות משניות (ילדים), שיורשות את התכונות של המחלקות הראשיות.
את ההגדרה של מחלקה יורשת נציין ע"י המילה extends.
לדוגמא, נבנה שלד של ירושה בתוך אוניברסיטה:

מחלקות יורשות

יצרנו פה 4 מחלקות: מחלקת person - מחלקת הבסיס שתייצג נתונים של ישות אנושית השייכת לאוניברסיטה: שם, ת"ז, וכו'; ו 2 מחלקות בנים, שירשו אותה: מחלקת student, שתייצג תכונות נוספות הקשורות בסטודנטים (שם המחלקה שבה הוא לומד, מספרי הקורסים שהוא לומד, חובותיו הכספיים, וכו') ומחלקת employee, שתייצג תכונות נוספות הקשורות בעובדי האוניברסיטה (סוג המשרה, שעות עבודה, משכורת, וכו'). למחלקת employee בעצמה ניצור מחלקת בת שתירש אותה - lecturer - שתייצג תכונות נוספות, הקשורות במרצים (תואר, איזה קורסים מלמד, שעות קבלה, וכו').
כל עצם מסוג student שניצור יקבל אוטומטית גם את תכונות העצם מסוג person, כלומר גם לו יהיו התכונות של: שם, ת"ז, וכו'.
לכל מחלקה נגדיר כמובן גם פונקציות מתאימות.
הירושה מתאימה לנו מכמה סיבות:

  1. דימוי העולם האמיתי בכתיבת התוכנית - מקל על ההבנה.
  2. כתיבה קצרה יותר - אין צורך לכתוב תכונות זהות לכל מחלקה ומחלקה. מספיק להגדיר מחלקת בסיס אחת לכולם.
  3. היכולת להרחיב את הקוד הקיים כבר במחלקות הספריה.
  4. פולימורפיזם - יצירת ממשק אחיד לעצמים ממחלקות נגזרות.
  5. ביטחון - ניתן לקבוע שאי אפשר יהיה לשנות (תלוי בהגדרת קשר הירושה) את נתוני מחלקת הבסיס ממחלקות חיצוניות, שאליהן אפשר להגביל את הגישה.

הרחבת הדוגמא והמילה השמורה super

כעת נרחיב את הדוגמא שהבאנו, ונכתוב את הקוד.
נתחיל בכתיבת הקוד של מחלקת הבסיס:
מחלקת person
הסבר: כתבנו את מחלקת הבסיס, הכוללת שני משתנים: שם ות"ז, והוספנו לה שתי פונקציות: פונקצית קונסטרקטור ופונקצית הדפסה (print), שעושה שימוש בפונקציות ההדפסה של ג'אווה.
כעת נרחיב את קוד אחת המחלקות היורשות:
מחלקת student

הסבר: כתבנו את הקוד למחלקת student, שיורשת את מחלקת הבסיס person ומוסיפה עוד 3 משתנים משל עצמה: שם מחלקה, מספר קורס, והתשלום שעל הסטודנט לשלם. כל עצם מסוג student שניצור בתוכנית עצמה יהיה מורכב מחמשה משתנים: 2 משתנים ממחלקת הבסיס ו 3 פרטיים של מחלקת student. הוספנו גם עוד שתי פונקציות - קונסטרקטור ופונקצית הדפסה. בקונסטרקטור השתמשנו במילה השמורה super. ע"י המילה השמורה הזאת אנו פונים לפונקצית הבסיס, ומשתמשים בקונסטרקטור שלה ע"מ להכניס את 2 הנתונים הראשונים. בדומה, בפונקצית ההדפסה השתמשנו ב super כדי לקרוא לפונקצית ההדפסה של person, ולאחר מכן נדפיס גם את שאר הנתונים של הסטודנט.


למעלה

כללי תורשה

  • כל תכונה שקיימת במחלקת הבסיס מועברת בירושה למחלקה היורשת.
  • המחלקה הנגזרת מגדירה תכונות נוספות לעצמה, בלי קשר למחלקת הבסיס.
  • מחלקה יורשת יכולה להגדיר פונקציות חדשות, גם בעלות שם זהה לפונקציות שניתנו ע"י מחלקת הבסיס, וכך לדרוס את אותן פונקציות.
  • תמיד בקריאה לפונקציה תיבחר הגירסה העדכנית ביותר שלה.
  • בג'אווה, בניגוד ל ++c, אין אפשרות לרשת מכמה מחלקות שונות.


למעלה

פולימורפיזם

פולימורפיזם - רב צורתיות

הרעיון הכללי: להתייחס לעצמים שונים בתור דברים דומים.
נדגים את הרעיון: יש סוגים שונים של מחשבים: מחשב אישי, מחשב כף יד, מחשב שרת, מחשב על וכיו"ב. אם נרצה לדבר על כל אחד בנפרד נגדיר אותם בנפרד. אבל, נוכל גם לדבר עליהם בתור מחשבים סתם. כלומר, למרות שהם שונים יש ביניהם מכנה משותף, שאליו נוכל להתייחס.
נסתכל על הדוגמא הקודמת: הגדרנו שם סוג בסיסי, person, ושלושה סוגים נגזרים, student, employee ו lecturer. נניח שנרצה ליצור מערך, שיכיל את כל הישויות שקיימות באוניברסיטה. לא נוכל לבנות מערך של סטודנטים, עובדים או מרצים, מאחר ושתי הישויות האחרות לא תוכלנה להיות חלק מהמערך. אבל, אם נבנה מערך מסוג person, נוכל להשתמש בעיקרון הפולימורפיזם, לפיו:

  • מצביע על עצם בסיס יכול להצביע בהתאם גם על עצם של מחלקה שנגזרה ממחלקת הבסיס;
  • כל פונקציה בג'אווה מוגדרת כפונקציה וירטואלית (בניגוד ל ++c ששם צריכה להיות הגדרה מפורשת). כלומר, הפונקציה תקרא לעצם לפי מה שהוא באמת ולא תלך לפונקציות של מחלקת הבסיס.

לשם דוגמא נגדיר מערך מסוג person:

מערך person
אבל העצמים שנציב יהיו מסוגים שונים:
הצבה במערך - פולימורפיזם

וכשנקרא לפונקצית ההדפסה יודפסו פונקציות ההדפסה המתאימות, בזכות תכונת הוירטואליות, ולא פונקצית ההדפסה של person.

הסבר לתכונת הוירטואליות

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

פולימורפיזם שגוי

תגרור הודעת שגיאה.



למעלה

מניעת ירושה

ניתן להגדיר פונקציה כאחרונה בסדר הירושה, ע"י שימוש במילה final.
לדוגמא נגדיר את המחלקה lecturer כאחרונה:

דוגמה לשימוש ב final

כלומר, לא ניתן להגדיר יותר מחלקה נוספת שתירש את מחלקת lecturer.
בדומה אפשר להגדיר פונקציה כלא ניתנת לדריסה, ע"י שימוש באותה המילה השמורה final. לדוגמא:

דוגמה נוספת לשימוש ב final

אנחנו מגדירים את הפונקציה print במחלקת worker כ final, ולכן לא נוכל לדרוס אותה ולהגדיר פונקצית הדפסה מיוחדת למחלקת lecturer.


למעלה

פולימורפיזם מופשט

מחלקות מופשטות

בג'אווה ניתן ליצור מחלקות בסיס, שבהן תעשה רק הגדרת פונקציות ולא יהיה ניתן ליצור עצמים של הפונקציה, כלומר הפונקציה תהיה "מופשטת - ווירטואלית טהורה" ותהווה בסיס לפונקציות שירשו ממנה.
פונקציה כזאת ניצור ע"י שימוש במילה abstract.
לדוגמא, לא נרצה שמישהו ייצור עצם מסוג person בדוגמא שהבאנו, מאחר והוא אינו מתייחס לשום ישות ספציפית, לכן נוכל להגדיר מחדש את המחלקה כך:

הפשטת person

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

פונקציות מופשטות

בתוכנית שכתבנו, מימשנו פונקצית הדפסה לכל אחת מהמחלקות שירשו את person, חוץ מלמחלקת lecturer. מתכנת שיכתוב לתוכנית עלול לשכוח את זה ולשלוח נתונים להדפסה לעצם מסוג lecturer, שלו "שכחנו" ליצור פונקצית הדפסה. כדי למנוע מצב כזה ניתן להגדיר במחלקת הבסיס פונקציה מופשטת, ריקה לחלוטין, בעזרת השימוש במילה השמורה abstract.
הגדרת הפונקציה כאבסטרקטית (מופשטת), מחייבת כל מחלקה שיורשת את המחלקה, שבה הפונקציה המופשטת מוגדרת, להגדיר פונקציה בעלת אותו שם, שתממש אותה.
כלומר, אם ברצוננו לחייב את המתכנת להגדיר פונקצית הדפסה לכל אחת מהמחלקות שיורשות את מחלקת הבסיס person, היינו צריכים להגדיר פונקצית הדפסה מופשטת במחלקת הבסיס:

פונקציית הדפסה מופשטת

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

למעלה

ממשקים (interfaces) - ומימוש ממשקים (implements)

מחלקה מופשטת, שכל הפונקציות שלה מופשטות, תוגדר ע"י שימוש במילה השמורה interface, במקום השימוש בclass.
זהו בעצם סוג חדש של מחלקה, שיותר מגדיר ממשקים של תוכניות מאשר מחלקות ממש.
אי אפשר ליצור עצמים של interfaces וכל מחלקה שתירש אותו תהיה חייבת לממש את כל הפונקציות שלו.
נניח לדוגמא שנרצה לקבל אישור בכתב לאחר כל הוספת אישיות כלשהי. נשנה את התוכנית הקודמת, ונגדיר מחלקה חדשה ack:

ממשק ack

הממשק מכריז על פונקציה שאינה מקבלת פרמטרים ותמומש על ידי כל אחת מהמחלקות היורשות.
מימוש ממשק יעשה ע"י המילה השמורה implements:

דוגמה למימוש של ack

אפשר לומר, שמנגנון הממשקים הוא כמו מנגנון ירושה נוסף. המחלקה יכולה לרשת תכונות וירטואליות של ממשק, בנוסף לירישת תכונות "ממשיות" ממחלקה רגילה. לכן אפשר גם לתת תכונות ממשקיות, בנוסף לפונקציות, וגם אותן ירשו המחלקות שממשות את הממשק. אבל כל התכונות בממשק מוגדרות אוטומטית כ public static final, כלומר הן סטטיות וקבועות לכל אורך התוכנית ולא ניתנות לשינוי במשך התוכנית.
בניגוד לירושה רגילה, בה כל מחלקה יכולה לרשת רק מחלקה בודדת, כל מחלקה יכולה לממש ממשקים רבים.
כך
מימוש מרובה

כלומר: מחלקה a יורשת את מחלקה b ומממשת את הממשקים c ו d.
כל פונקציה בממשק מוגדרת כ public, אפילו אם המתכנת לא כתב זאת. לכן מימוש של הפונקציה יהיה גם הוא public.

למעלה

האופרטור instanceof

בג'אווה קיים מנגנון (RTTI (Runtime Type Information, שמאפשר לקבל מידע על מצב התוכנית בזמן הריצה. חלק מהמנגנון הוא האופרטור instanceof.
האופרטור מקבל שני פרמטרים: עצם ומחלקה כלשהי בתוכנית, כך:
<object> instenceof <class>
ומחזיר ערך אמת באחד משלושת המקרים:

  • העצם הוא מופע של המחלקה;
  • העצם הוא מופע של מחלקה שירשה את המחלקה הנתונה;
  • העצם הוא מופע של מחלקה שממשה את הממשק הנתון;
למשל אם נגדיר את העצם הבא:
;student s
האופרטור instenceof יחזיר ערך אמת על כל אחד משלושת הביטויים הבאים:
s instenceof student
s instenceof person
s instenceof ack

האופרטור הנ"ל יוכל לעזור לנו בתוכנית לעשות פעולות עם כל העצמים שיורשים את person, לדוגמא.




למעלה