PracticU

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

בלי כתוביותעם כתוביות
PracticU › קורס תכנות מונחה עצמים - OOP - Object Oriented Programming - חומר לימוד של מדעי המחשב › רב-צורתיות פולימורפיזם - Polymorphism
מילון מושגים:
פולימורפיזם - עיקרון בתכנות מונחה עצמים שבו אובייקטים ממחלקות שונות מגיבים לאותה פעולה כל אחד בדרכו.
אובייקט - יחידה בתכנות מונחה עצמים שמאגדת נתונים (תכונות) ופעולות (מתודות). נוצר בדרך כלל ממחלקה המשמשת כתבנית.
פונקציה - קטע קוד בעל שם שמבצע משימה מוגדרת, מקבל ערכי קלט (פרמטרים) ויכול להחזיר תוצאה. מאפשרת לארגן קוד ולהשתמש בו שוב.
זיכרון - רכיב מהיר ונדיף שבו המחשב שומר נתונים ותוכניות בזמן ריצתם. תוכנו נמחק עם כיבוי המחשב.
מחלקה - תבנית בתכנות מונחה עצמים שמגדירה אילו נתונים ופעולות יהיו לאובייקטים שנוצרים ממנה.

רב-צורתיות פולימורפיזם - Polymorphism
השיעור מסביר פולימורפיזם, כלומר רב-צורתיות: כשרפרנס מטיפוס בסיס מצביע על אובייקט נגזר, השאלה היא איזו פונקציה תתבצע.
הבעיה
1. פונקציה רגילה: מחלקת Shape מכילה פונקציה print. כשהרפרנס מטיפוס Shape מצביע על Circle, מופעלת הפונקציה של Shape, ולא זו של Circle. הטיפוס של הרפרנס קובע, ולא האובייקט שבזיכרון.
2. בדיקה בדיבאגר: בהרצה עם דיבאגר רואים שהקפיצה היא לפונקציה של Shape, גם כשהאובייקט בזיכרון הוא מלבן או עיגול.
הפתרון: פונקציה וירטואלית
1. virtual במחלקת הבסיס: הפונקציה מוגדרת עם המילה virtual.
2. override במחלקות היורשות: המחלקה היורשת דורסת את המימוש ונותנת מימוש משלה.
3. התוצאה: כשקוראים לפונקציה דרך רפרנס בסיס, תמיד מתבצעת הפונקציה של האובייקט האמיתי שבזיכרון.
מתי להגדיר וירטואלית
1. כשצפויה דריסה: פונקציה שמתעסקת בנתונים שהמחלקות היורשות משנות, כמו print, צריכה להיות וירטואלית.
2. כשאין דריסה: פונקציה שמחשבת את המרחק מראשית הצירים עם Math.Sqrt נשארת רגילה, כי כל הצורות משתמשות באותו חישוב.
שימו לב
דריסה לא מחייבת לוותר על המימוש של הבסיס. מחלקה יורשת יכולה לקרוא ל-base.print() ולהוסיף עליו, או לכתוב מימוש חדש לגמרי. כך אפשר לכתוב פונקציה אחת שעובדת על כל הצורות, והמימוש הנכון יופעל לפי האובייקט האמיתי.
השיעור הבא בקורס: תכנות מונחה עצמים - עקרון פולימורפיזם

פרקי ההרצאה: רב-צורתיות פולימורפיזם - Polymorphism

  1. 00:00

    מעבר לפולימורפיזם

  2. 02:04

    פונקציה הטרוגנית ובעיית ה-Print

  3. 02:41

    ה-Print של כל מחלקה

  4. 04:12

    איזה Print רצוי שיתבצע

  5. 05:25

    הרצה המימוש של Shape מתבצע

  6. 09:01

    Reference מהטיפוס הנכון

  7. 11:22

    מתודה רגילה אינה דורסת

  8. 14:34

    הפתרון פולימורפיזם ו-override

תמלול ההרצאה: רב-צורתיות פולימורפיזם - Polymorphism

הטקסט המלא של הדקות הראשונות בהרצאה «רב-צורתיות פולימורפיזם - Polymorphism» בקורס הווידאו של PracticU – כתוביות בעברית עם זמן התחלה לכל פסקה. כך תוכלו לקרוא ולחפש את הנושאים לפני שמתחילים ללמוד.

  1. 00:14

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

  2. 00:46

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

  3. 00:56

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

  4. 01:09

    כאן אנחנו רואים שהכנסנו אובייקט shape, פה הכנסנו אובייקט circle, פה הכנסנו אובייקט rectangle בגלל שכל תא במערך הוא reference מטיפוס shape. פה אנחנו רואים שהשתמשנו בחוק הזה בכדי להגדיר רפרנס אחד ויחיד שכתלות מהמשתמש יכול לקבל פעם אחת סרקל או פעם אחרת ריקטנגל וככה אנחנו קבלים רפרנס אחד שיתעסק עם זה.

  5. 01:34

    אז מה זה פולימורפיזם? אז לצורך ההבנה של מה זה פולימורפיזם שבעברית זה רב-צורתיות אנחנו הולכים לבדוק כל אחד מהמקרים האלה שכרגע עשינו ומה שאני רוצה לעשות זה פשוט להגדיר מעין ערכים דיפולטיים לכל אחד מהאובייקטים

  6. 01:59

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

  7. 02:29

    עכשיו בואו נבחר רגע מה יש בפרינט אז אנחנו רואים ש-S נולד מטיפוס שייפ אם אנחנו מסתכלים על המחלקה שייפ אנחנו יכולים לראות שבשייפ יש רק X ו-Y ולכן פרינט מציגה רק את ה-X ואת ה-Y כלומר הפרינט של שייפ מציגה כמובן X ו-Y בלבד אבל הפרינטים האחרים, לדוגמה הפרינט של סרקל מציגה גם את ה-X ואת ה-Y שהיו

  8. 03:02

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

  9. 03:34

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

  10. 03:57

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

  11. 04:09

    יש לנו פונקציית פרינט אחת שאנחנו רוצים שתתבצע, והשאלה היא איזה. אז קודם כל היינו רוצים שהפרינט שתתבצע תהיה זאת ששייכת למחלקה שממנה בנוי האובייקט בזיכרון ולא למחלקה של ה-reference.

  12. 04:32

    זה היה הגיוני במקרה הזה, מפני שאם האובייקט שלי הוא circle, אני רוצה להציג את הפרטים של circle, אני לא רוצה להציג רק את הפרטים של shape. אם האובייקט שלי הוא rectangle, אני רוצה להציג את הפרטים של rectangle, אני לא רוצה להציג את הפרטים של shape, רק בגלל שה-reference הוא shape.

  13. 04:51

    הרי מה הסיבה שה-reference הוא של shape? הסיבה היא שהרפרנס שייך למחלקה שייפ ונוצר ממחלקה שייפ זה רק בכדי שנוכל לשלוח לפונקציה כל צורה שהיא בעולם.

  14. 05:07

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

  15. 05:21

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

  16. 05:55

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

  17. 06:24

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

  18. 06:42

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

  19. 07:18

    פה יש לנו אחד אחד, 2,2,2 ו-3,3,3,3 האם עכשיו פונקציית הפרינט שקיימת כאן תתבצע עבור האובייקט האמיתי שהמערך מצביע עליו או שעדיין שייפ יתבצע?

  20. 07:39

    אז התשובה היא עדיין שייפ יתבצע בואו נריץ ושוב פעם נראה שאנחנו מקבלים רק x וy אנחנו לא מקבלים את ה-radius את ה-width ואת ה-height זאת אומרת שגם במקרה הזה אנחנו אפילו יכולים לשים פה breakpoint נריץ את זה, ניכנס לברייק פוינט ונראה שמתבצעת קפיצה לפונקציה פרינט של המחלקה שייפ.

  21. 08:08

    בואו נמשיך. נראה שגם בפעם הבאה כאשר i ערכו עובר להיות 1 ואני יודע לפי השורה הזאת שבתא מספר 1 יש לנו סרקל. אם אני אמשיך ואכנס עם F11 אני אראה שהמימוש שמתבצע שייך לשייפ.

  22. 08:31

    בואו נמשיך. נראה שגם ש-i ערכו עולה ל-2 וב-2 יש לנו ריקטנגל אנחנו רואים שפרינט מתבצעת עבור שייפ. זאת אומרת אין כאילו בכלל מימוש של הפונקציה פרינט שקיימת בסרקל ושקיימת בריקטנגל כאשר הרפרנס הוא של מחלקת הבסיס.

  23. 09:01

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

  24. 09:37

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

  25. 09:54

    קור להקדמה הזאת בעצם מראה לנו שיש פה איזושהי בעיה שנוצרת כאשר רפרנס מטיפוס בסיס מצביע על אובייקט נגזרת. ובכדי להוכיח לכם שהבעיה הזאת קיימת רק במקרה הזה בואו שנייה אחת ניצור circle c שווה new circle c.print מריץ ונראה שהכל בסדר.

  26. 10:28

    ניצור rectangle r שווה new rectangle r print, ונראה שהכל בסדר. זאת אומרת, אם ה-reference הוא מהטיפוס הנכון, אנחנו רואים שהיה לנו בעיה.

  27. 10:48

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

  28. 11:00

    לצורך כך אנחנו צריכים להבין איך נראית פונקציית print. אנחנו רואים שכשבנינו את פונקציית פרינט, בנינו אותה בצורה רגילה לחלוטין.

  29. 11:08

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

  30. 11:25

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

  31. 11:57

    אותו השם ואותה החתימה הדבר הזה לא נקרא דריסה כלומר המתודה ששייכת למחלקה היורשת לא דורסת את המתודה ששייכת למחלקת הבסיס ולכן לא מחליפה את המימוש שלה.

  32. 12:21

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

  33. 12:53

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

  34. 13:09

    בואו נשים פה c2 r2 ונראה שאותו חוק פעל גם כאן. למה אותו חוק פעל גם כאן? כי יש לנו כאן רפרנס מטיפוס סרקל והחוק אמר שאם הפונקציה היא פונקציה רגילה אז המימוש שיתבצע שייך למחלקה של הרפרנס.

  35. 13:33

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

  36. 13:48

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

  37. 14:16

    נגזרת ואנחנו לא רוצים שהמימוש של הפונקציות יהיו אלו ששייכות לרפרנס כי התפקיד שלו זה רק להחזיק כתובת התפקיד שלו זה במקרה הזה לא להוות מקור לפונקציות אז מה הפתרון?

  38. 14:32

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

  39. 14:58

    זאת אומרת אם בשייפ היה לנו הצגה רק של x ו-y בסרקל אנחנו נותנים מימוש אחר. המימוש אחר אומר לך לבסיס תעשה את מה שהוא עושה וגם תעשה עוד דברים.

  40. 15:12

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

  41. 15:22

    אבל הדבר החשוב פה הוא המימוש הזה הולך להשתנות. כלומר, הולך להחליף את המימוש של מחלקת הבסיס.

לקורס המלא: קורס תכנות מונחה עצמים OOP

ההרצאה הקודמת: מערכים - Array בקורס תכנות מונחה עצמים

ההרצאה הבאה: תכנות מונחה עצמים - עקרון פולימורפיזם

הרצאות קשורות מקורסים אחרים

  • פולימורפיזם ב-NodeJS
  • פולימורפיזם ב-TypeScript
  • רב-צורתיות, בדיקת סוג משתנה בזמן ריצה והמרת סוג המשתנה

קורסים נוספים במסלול

  • קורס יסודות התכנות - שפת C# .NET - קורס תכנות למתחילים - דוט-נט / סי-שארפ - חומר לימוד של מדעי המחשב