הורשה   ופולימורפיזם
Download
1 / 39

הורשה ופולימורפיזם - PowerPoint PPT Presentation


  • 85 Views
  • Uploaded on

הורשה ופולימורפיזם. צורה. ריבוע. משושה. עיגול. מוטיבציה: אפליקציית חלונות טיפוסית – נע זרת בפקדים ( Widgets ). Form. Button. Label. Textbox. פקדים הם אובייקטים לכל דבר, ומוגדרים בספריות מיוחדות שמספקות ממשקי GUI . לפקדים שראינו יש תכונות משותפות: קוארדינטות x,y על גבי הטופס.

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' הורשה ופולימורפיזם' - aloysius-caleb


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
הורשה ופולימורפיזם

צורה

ריבוע

משושה

עיגול


מוטיבציה:אפליקציית חלונות טיפוסית – נעזרת בפקדים (Widgets)

Form

Button

Label

Textbox


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

  • לפקדים שראינו יש תכונות משותפות:

    • קוארדינטות x,y על גבי הטופס.

    • אורך ורוחב.

    • טקסט.

  • כמו כן אפשר לחשוב על פונקציות משותפות:

    • שינוי טקסט/גודל/מיקום.

    • הסתרה.

    • נעילה.

    • ציור.


  • היינו רוצים להגדיר אובייקט שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

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

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

  • בשביל מה זה טוב?


ירושה - מוטיבציה שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

  • אובייקט טופס (form) מכיל רשימה של כל הפקדים שנמצאים על הטופס.

  • מימוש:

    ב-C:רשימה לכל אחד מסוגי הפקדים האפשריים או רשימה של void* (כאשר נצטרך לזכור איכשהו מה הטיפוס האמיתי של כל איבר ברשימה).

    ב-C++: רשימה של אובייקטים מסוג "פקד".


ירושה - מוטיבציה שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

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

  • פיתרון:ב-C: מבצעים קריאה מיוחדת לפונקצית הציור לכל סוג אפשרי של פקד.

    ב-C++: נותנים את אותה פקודת ציור לכולם, אבל "בדרך קסם" כל אחד מהם יפעל בצורה המתאימה לו (עוד על הקסמים הללו בעוד מספר שקפים)


הורשה שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

הורשה הינה הדרך בה מביעים יחסי is-a בתוכנית.

נעזרים בהורשה לצורך שתי מטרות שונות:

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

  • polymorphic behavior - כאשר נרצה כי מספר מחלקות יחשפו ממשק זהה אך יתנהגו בצורה שונה.

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


סינטקס של ירושה: שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

class widget {

int xCoordinate, yCoordinate;

String text; … // more fields common to all widgets

public:

void draw(); void hide(); … // more functionality // common to all widgets

};

class button: public widget {

… // additions and specifications

void push();

};

סינטקס של ירושה public - סוג הירושה היחיד שנלמד בקורס הזה


מה זה שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.is-a?

  • בשקף הקודם "כפתור" יורש מ"פקד". במקרה זה אומרים ש"כפתור is-a פקד" (באנגלית זה נשמע יותר טוב). בעברית: "כפתור הוא סוג מיוחד של פקד".

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

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

  • כל זה נכון להורשה ציבורית (public) – היחידה שנלמד בקורס הזה.


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

נרצה להוסיף למחלקה Stack הידועה את הפונקציה:

  • (popk(int k - אשר תוציא מהמחסנית k איברים.

    למחלקה המשופרת נקרא MultiStack.

    קיימות שלוש דרכים לבצע זאת:

  • לכתוב את MultiStack מהתחלה (copy & paste).

  • להיעזר ב-Stack כשדה.

  • לרשת מ-Stack.


Stack i

class MultiStack { שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

public:

MultiStack(int sz) : _s(sz) {}

~MultiStack() {}

void push(int i){_s.push(i);}

void pop() { _s.pop() ; }

int top() { return _s.top();}

bool isEmpty() {

return _s.isEmpty(); }

void popk(int k) ;

private:

Stack _s;

};

חסרונות/פגמים/ליקויים/מגרעות...

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

לקורא הקוד לא ברור מהתוכנית כי MultiStack היא סוג מיוחד של מחסנית.

כיצד נראה המימוש של popk? לא נוכל לייעל את העבודה ע”י גישה ישירה למבני הנתונים של המחסנית (האם friend הוא פתרון ?)

שימוש באובייקט Stack כשדה I


Stack ii
שימוש באובייקט שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.Stack כשדה II

  • בעיה נוספת לשיטה זו: אם יש לנו קוד כתוב אשר נעזר במחסניות (מקבל כפרמטר מצביע או reference לאובייקט מטיפוס Stack) לא נוכל לשלוח לו MultiStack כפרמטר כי MultiStackאינו Stack.

    int sumAndPop(Stack & stack) {

    int sum = 0;

    while (!stack.isEmpty()) {

    sum+=stack.top();

    stack.pop() ;

    }

    return sum;

    }


Stack

class שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.MultiStack:

public Stack {

public:

MultiStack (intsz) :

Stack(sz) {}

~MultiStack() {}

popk(int k) ;

};

בכדי להביע כי MultiStack הינו סוג מיוחד של Stack נגדיר את MultiStack כיורש מ-Stack.

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

הפיתרון הקל והנכון - ירושה מ Stack


Stack1
הורשה מ- שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.Stack

  • ירושה כ-public (היחידה אותה נלמד) גורמת לכך שכל משתמש של המחלקה MultiStack יוכל להיעזר במתודות ה-public של המחלקה MultiStackוגם במתודות של Stack (כאילו הוגדרו כ-public ב-MultiStack).

  • כל משתמש של Stack יוכל להיעזר ב- MultiStack כב-Stack רגיל.


Protected fields
protected fields שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

הרשאות גישה וירושה

  • שדות (ופונקציות) private ניתנים לגישה ע”י פונקציות של המחלקה ו friends.

  • שדות (ופונקציות) protected ע”י הנ"ל + מחלקות יורשות

class Stack {

public : ….

protected :

int* array;

int topIndex, size ;

};

  • איך נראית הפונקציה popk? האם ניתן לגשת לשדות המימוש של המחלקה Stack ?

  • כדי שבמחלקה היורשת ניתן יהיה לגשת לשדות של האב יש להגדיר את השדות כ protected.


גישה לשדות בהורשה שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.)מסוג (public

Stack

Private Members

Protected Members

Public Members

MultiStack

Private Members

Protected Members

Public Members

User

MultiMultiStack

Private Members

Protected Members

Public Members


קונפליקט של שמות שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

class A {

public:

voidf( );

void g(intx);

};

class B: public A {

public:

voidf( );

void g(intx);

};

  • הגדרת מתודה עם שם זהה למתודה שקיימת במחלקת האב "תבטל" (override) את המתודה של האב במחלקה היורשת.

  • כיצד ניגש ל-member function(מתודה) של מחלקת האב כאשר יש פונקציה עם שם זהה במחלקת הבן?

void B::g(int x){

f( ); //B גישה לפונקציה של

A::g(x); //A גישה לפונקציה של מחלקת הבסיס

}


הורשה - בונים והורסים שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

  • בכל פעם שיוצרים אובייקט מהסוג היורש אנו בעצם יוצרים גם אובייקט מסוג האב. זהו המימוש ב ++C.

  • אתחול שדות האב (ע”י ה-C’tor של האב, שנקרא דרך שורת האתחול של הבן) נעשה לפני יצירת שדה כלשהו של בנו, ובפרט לפני שנקרא ה-c’tor של הבן.

  • הריסת האב נעשית לאחר הריסת בנו ובפרט אחרי ה-d’tor של הבן.

שדות של האב

אובייקט אב

אובייקט בן

שדות של הבן


Instance
סיכום: סדר בניה של שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.instance של מחלקה

סדר הבנאים בקריאה לבנאי של מחלקה (כולל copy c’tor):

  • בנאי מחלקת אב (אם יש כזאת)

  • בַּנַאֵי השדות - לפי סדר ההצהרה עליהם במחלקה (ולא לפי סדר הקריאה לבנאים שלהם ברשימת האתחול)

  • גוף הבנאי של המחלקה.

הסדר בהריסה: הפוך.


הורשה פולימורפית שייקרא "פקד" ויכיל את כל התכונות והפונקציונאליות המשותפות הללו.

  • בדוגמאות הקודמות האב והבן חלקו את אותה התנהגות. התנהגות זו הייתה תקפה לשתי המחלקות ולכן שתי המחלקות חלקו:

    • את אותו ממשק: אותה חתימה וערך מוחזר של הפונקציה.

    • את אותו מימוש: אותו קוד פעל בשניהם.

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


מחלקות מעולם החי כדוגמא למחלקות בעלות התנהגות פולימורפית.

חיה

משקל , גובה , ת. לידה

הדפס סוגי מזון

השמע קול


class Animal { בעלות התנהגות פולימורפית.

public:

int getAge() ;

virtual void makeSound();

private:

int weight,age ;

};

class Dog : public Animal{

void makeSound() ;

} ;

כל בעלי החיים חולקים דברים משותפים:

לכולם יש משקל, גובה, גיל

כולם אוכלים ומשמיעים קולות

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

יחד עם זאת לאפשר גמישות של התנהגות שונה כאשר הדבר נדרש

מחלקות בעלות התנהגות פולימורפית I

גם וירטואלית, בגלל שהפונקציה באבא וירטואלית


מחלקות בעלות התנהגות פולימורפית בעלות התנהגות פולימורפית. II

  • ניתן במחלקה היורשת ממחלקה אחרת להגדיר מחדש פונקציות אשר הוגדרו במחלקת האב.

  • אולם הדבר יגרום לכך שהפונקציה שתיקרא תלויה בדרך בה קראנו לה:

    Dog * d = new Dog();

    Animal * a = d ;

    a->makeSound(); d->makeSound() ;

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


מחלקות בעלות התנהגות פולימורפית בעלות התנהגות פולימורפית. III

class Cat : public Animal{

public:

void makeSound() {

cout << “miao\n”;

}

} ;

class Dog : public Animal{

public:

void makeSound() {

cout << “vuf vuf\n”;

}

} ;

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


void foo () { בעלות התנהגות פולימורפית.

const int arraySize = 3;

Animal* animals[arraySize];

animals[0] = new Dog();

animals[1] = new Cat();

animals[2] = new Bear();

for(int i = 0; i < arraySize; ++i) {

animals[i]->makeSound();

}

}

פלט התוכנית

vuf vuf

miao

orrrr

מחלקות בעלות התנהגות פולימורפית IV


שיטה חליפית להתנהגות פולימורפית בעלות התנהגות פולימורפית.

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

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


Abstract classes
Abstract Classes בעלות התנהגות פולימורפית.

  • קיימות מחלקות המייצגות רעיון מופשט ויש להן משמעות רק כבסיס למחלקות אחרות

  • מחלקה עם מתודה Pure Virtual (אחת או יותר) היא מחלקה אבסטרקטית

  • לא ניתן ליצור אובייקטים של מחלקה שכזו

class Shape {

public:

Shape(int x, int y) : center_x(x),center_y(y){}

virtual double area() const = 0;

protected:

int center_x, center_y ;

};

Pure Virtual Method


דוגמא מסכמת: צורות בעלות התנהגות פולימורפית.

class Shape {

public:

Shape(int x, int y) : center_x(x),center_y(y){}

virtual double area() const = 0;

protected:

int center_x, center_y ;

};

class Circle: public Shape {

public:

Circle(int x, int y, int r): Shape(x,y), radius(r){}

double area() const {

return (PI*radius*radius);}

private:

int radius;

} ;

class Rectangle: public Shape {

public:

Rectangle(int x, int y, int h, int w): Shape(x,y),height(h),width(w) {}

double area() const

{return (height*width);}

private:

int height, width;

} ;


int בעלות התנהגות פולימורפית. main() {

Shape* shapes[N];

// an array of rectangles & circles

// initialization

double totalArea=0;

for (int i = 0; i < N; ++i) {

totalArea += shapes[i] ->area( );

}

}

דוגמא מסכמת: צורות


Exceptions
Exceptions בעלות התנהגות פולימורפית.

  • הסטייל החדש לטיפול בשגיאות

BAD_ARG

catch

main()

return BAD_ARG

throw

func()


חריגות - מוטיבציה בעלות התנהגות פולימורפית.

  • פונקציה שאמורה להחזיר ערך מטיפוס כלשהו, ויכולה להחזיר את כל טווח הערכים של הטיפוס, איך תבטא שגיאה?פיתרונות ב-C:

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

  • הרחבת טווח ערכי ההחזרה ושמירה על ערכים מיוחדים (מחוץ לטווח טיפוס ההחזרה המקורי) לשגיאה.

  • החזרת טיפוס מיוחד.

int function(…)  int function(…, Result* res)

int function(…)  long long int function(…)

int function(…)  Result function(…)


חריגות – מוטיבציה (המשך) בעלות התנהגות פולימורפית.

2. איזה ערך שגיאה תחזיר פונקציה שאינה מחזירה אף ערך? למשל, מה אמור להחזיר c’tor במקרה של כישלון בהקצאת זיכרון (דינמי) לשדותיו, לדוגמה?

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

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

?


חריגות – מוטיבציה (המשך) בעלות התנהגות פולימורפית.

4.אם פונקציה a קוראת פונקציה b שקוראת לפונקציה c וכך הלאה והתגלתה שגיאה בפונקציה z אשר רק פונקציה a אחראית\יודעת איך לטפל בה, אנחנו כמתכנתים נצטרך לכתוב כמות (עצומה) של קוד "לטפל מבלי לטפל" בשגיאות ברמות הביניים, שאינן יודעות\מעוניינות לטפל בשגיאות.

Function Calls:

a() => b() => …. =>z()

Error found in z() only possible/correct to deal with at a().


חריגות – סמנטיקה בעלות התנהגות פולימורפית.

  • הרעיון: לאפשר העברת ערך שגיאה לרמה שיכולה ומעוניינת לטפל, תוך דילוג על שלבי הביניים.

  • בכל פעם שנרצה להודיע על שגיאה, "נזרוק" אובייקט שמייצג את השגיאה. זריקה זו תגרום לעלייה במעלה היררכיית הקריאה לפונקציות (תוך שחרור משתנים מקומיים בעזרת ה-d’tor שלהם) עד לבלוק ה-try-catch המכיל אותה הראשון שמתייחס לשגיאה זו.


Try catch throw

חריגות – סינטקס בעלות התנהגות פולימורפית.

Try-catch-throw

  • try - הגדרת בלוק בו ייתכנו Exceptions

  • catch – תפיסת Exception

  • throw – זריקת Exception

try {

throw 20;// or more interesting code that might throw()

}

catch (int param) { cout << "int exception"; }

catch (char param) { cout << "char exception"; }

catch (...) { cout << "default exception"; }


חריגות – סינטקס (המשך) בעלות התנהגות פולימורפית.

תיקון לאופרטור[ ] שהגדרנו ל-String:

אחרי(שיעור 11):

לפני (שיעור 9):

void error(const char* str) {

cerr << str << endl;

exit(1);

}

char String::operator[](int i) const {

if ((i < 0) || (i > length)){

throw "String: index out of range";

}

return value[i];

}

char String::operator[](int i) const {

if ((i < 0) || (i > length)){

error("String: index out of range");

}

return value[i];

}


חריגות (המשך) בעלות התנהגות פולימורפית.

  • הערות חשובות:

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

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

  • מותר לזרוק כל טיפוס, כאשר בתפיסתו, אם משתמשים ב-by value semantics, ייקרא ה-copy c’tor.


מה פלט התוכנית? בעלות התנהגות פולימורפית.

  • #include <iostream.h>

  • void foo()

  • {

  • throw "ERROR";

  • cout << “Is this code executed ?" << endl;

  • return;

  • }

  • int main() {

    • try{

    • foo();

    • } catch (const char* string){

    • cout << string << endl;

    • }

    • cout << "END OF PROGRAM" << endl;

    • return 0;

  • }


Exceptions1
Exceptions בעלות התנהגות פולימורפית.

C++’s answer to C’s if(malloc(…) == NULL)

// bad_alloc standard exception

#include <iostream>

#include <exception>

using namespace std;

int main () {

try {

for(;;){

new int[10000000];

}

} catch (exception& e) {

cout << "Standard exception: " << e.what() << endl;

}

}


ad