200 likes | 441 Views
תכנות מונחה עצמים והנדסת תוכנה – תשע"ד. פולימורפיזם ( Polymorphism ). 1. מבוא. פולימורפיזם (רב צורתיות) הוא מנגנון מרכזי בתכנות מונחה עצמים. המנגנון מאפשר לממש מבני נתונים ואלגוריתמים גנריים. דוגמאות: מבנה נתונים אחד שמכיל צורות שונות (מעגל, מלבן, קטע, משולש,...).
E N D
תכנות מונחה עצמים והנדסת תוכנה – תשע"ד פולימורפיזם (Polymorphism) 1
מבוא פולימורפיזם (רב צורתיות) הוא מנגנון מרכזי בתכנות מונחה עצמים. המנגנון מאפשר לממש מבני נתונים ואלגוריתמים גנריים. דוגמאות: מבנה נתונים אחד שמכיל צורות שונות (מעגל, מלבן, קטע, משולש,...). אלגוריתם להחלפת גלגל בכלי תחבורה כלשהו (משאית, אוטובוס, אופנוע,...). קוד אחיד לכל כלי תחבורה, שפועל באופן שונה על כל סוג. 2 2
מימוש פונקציה מחדש • ראינו שבירושה אפשר לממש פונקציות מחדש: class A { : void print() { cout << “A–print”; } }; class B : public A { : void print() { cout << “B–print”; } }; int main() { A a; B b; a.print(); // A-print b.print(); // B-print }
המרה בירושה • עצם b הוא סוג של a, לכן ההמרה הבאה תקינה: a = b; // the A part of b is assigned a.print(); // A-print • אותן המרות יתבצעו עבור מצביעים: A* aPtr = &a; B* bPtr = &b; aPtr->print(); // A-print bPtr->print(); // B-print aPtr = (A*)&b; // direct conversion from B* to A* aPtr = &b // same as before aPtr->print(); // A-print • דוגמא 13_5
המרה בירושה (המשך) • ההמרה ההפוכה אינה תקינה: b = a; // error (A is not kind of B) bPtr = &a; // the same error • דוגמא 13_6 • בפרט, אחרי המרה מ-B ל-A כבר לא ניתן לגשת לחלקים של B: aPtr = &b; aPtr->bFunction(); // error • דוגמא 13_7
המרה בירושה (המשך) aPtr = &b; aPtr->print(); // A-print • מופעלת הפונקציה של A. • aPtr מצביע על עצם של B (על החלק ה-A של b). כל המידע על העצם b נמצא במקום זה בזיכרון. • מבחינה טכנית יכולנו להפעיל פונקציות של B באמצעות aPtr.
פונקציה וירטואלית (virtual) • פונקציה וירטואלית תאפשר לגשת לפונקציות של B באמצעות aPtr: class A { : virtual void print(); }; class B : public A { : virtual void print(); // writing “virtual” here is optional };
פונקציה וירטואלית (המשך) int main() { : aPtr->print(); // A-print bPtr->print(); // B-print aPtr = &b; aPtr->print(); // B-print } • aPtr מתייחס לחלק ה-A של עצם של B. • באמצעות המצביע aPtr אפשר להפעיל פונקציה של B. • נקרא פולימורפיזם. • דוגמא 13_8_10
מנגנון הפולימורפיזם • לשם מה נזדקק למנגנון הפולימורפיזם? Base* basePtr; Derived1 d1; Derived2 d2; Derived3 d3; ,… basePtr = &d1; or basePtr = &d2; or basePtr = &d1; ,… basePtr->print(); // same code for activating different functions Base virtual void print() Derived1 virtual void print() Derived2 virtual void print() Derived3 virtual void print()
דוגמאות לשימוש בפולימורפיזם • מבנה נתונים כללי: מערך של צורות שונות והציור שלהן. • אלגוריתם כללי: חישוב המקסימום הנומרי של פונקציות. • אלגוריתם כללי: החלפת גלגל בכלי תחבורה כללי. • מבנה נתונים כללי: רשימה משורשרת של צורות שונות.
חזרה על הנקודות המרכזיות • הפעלת פונקציה וירטואלית בעזרת ערך: A a; B b; a.print(); // A-print a = b; a.print(); // A-print • הפעלה פונקציה וירטואלית בעזרת מצביע: A* aPtr; aPtr = &a; aPtr->print(); // A-print aPtr = &b; aPtr->print(); // B-print
חזרה על הנקודות המרכזיות (המשך) • הפעלה פונקציה וירטואלית בעזרת הפניה (דומה למצביע): A& aRef1 = a; aRef1.print(); // A-print A& aRef2 = b; aRef2.print(); // B-print
חזרה על הנקודות המרכזיות (המשך) • המרות לא חוקיות: b = a; // error bPtr = &a; // error bRef = a; // error • חוסר גישה: aPtr = &b; aPtr->bFunction(); // error • אם C יורשת מ-A ולא מממשת מחדש את הפונקציה print() (וירטואלית או לא), אז הקריאה אליה מתייחסת למימוש שהוגדר ב-A : aPtr = &c; aPtr->print(); // A-print
פונקציה הורסת וירטואלית • אובייקט של מחלקה יורשת עלול להשתחרר באופן חלקי בלבד במקרה הבא: class A { public: ~A() { cout << “A-dtor”; } }; class B : public A { public: B() : _p( new int(7) ) {} ~B() { cout << “B-dtor”; delete _p; } private: int* _p; };
פונקציה הורסת וירטואלית (המשך) int main() { A* aPtr = new B(); delete aPtr; } • מהלך התוכנית: יודפס A-dtor, וההקצאה ע"י _p לא תשוחרר. • פתרון: להגדיר את הפונקציה ההורסת של A כפונקציה וירטואלית. • פלט תקין: B-dtor, A-dtor (מימין לשמאל).
פונקציה הורסת וירטואלית (המשך) • פונקציות בונות ופונקציות השמה (set) לא יוגדרו כוירטואליות (עבור פונקציות בונות תתקבל שגיאת קומפילציה). • מומלץ להגדיר פונקציה הורסת וירטואלית במחלקת הבסיס גם במקרה שהיא אינה עושה דבר. הפונקציות ההורסות של המחלקות היורשות יבצעו שחרור זיכרון מלא.
מחלקה אבסטרקטית • לעיתים נרצה להגדיר מחלקות מופשטות לחלוטין שתפקידן להוות מכנה משותף למחלקות אחרות. • יצירת עצמים ממחלקה כזו היא חסרת משמעות. • מחלקה כזו תקרא מחלקה מורישה אבסטרקטית (abstract base class). • דוגמא: מחלקת Shape מהווה מכנה משותף למרובע, משולש, עיגול וכדומה. הגדרת עצם ממחלקה זו היא חסרת משמעות. ציור עצם ממחלקה זו היא פעולה חסרת משמעות.
מחלקה אבסטרקטית (המשך) • מחלקה תוגדר להיות אבסטרקטית אם לפחות אחת הפונקציות בה היא וירטואלית טהורה. • פונקציה וירטואלית טהורה היא פונקציה ללא מימוש במחלקה (אלא רק במחלקות היורשות ממנה). • הגדרת פונקציה וירטואלית טהורה: class Shape { : virtual void draw()=0; }; • המחלקה Shape היא מחלקה אבסטרקטית.
מחלקה אבסטרקטית (המשך) • לא ניתן להגדיר עצמים של מחלקה אבסטרקטית, אלא רק מצביע או הפניה לעצם ממחלקה אבסטרקטית. • כל מחלקה יורשת צריכה לספק מימוש לפונקציה הוירטואלית טהורה. • מה ההבדל בין פונקציה וירטואלית טהורה לבין מימוש ריק? • מחלקה יורשת שאינה מממשת פונקציה וירטואלית טהורה, הופכת אף היא למחלקה אבסטרקטית. • דוגמא לשימוש במחלקה אבסטרקטית ופונקציה וירטואלית טהורה. • דוגמא למחלקה יורשת שאינה מממשת פונקציה וירטואלית טהורה.