• בלוג
  • פולימורפיזם בקטנה

פולימורפיזם בקטנה

21/07/2018

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

אבל שניה קודם איניגו

1. פולימורפיזם וסוגי משתנים

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

Car *c = new Car();
Boat *d = new Boat();
Plane *p = new Plane();

cout << c.maxSpeed();
cout << d.maxSpeed();
cout << p.maxSpeed();

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

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

לדוגמא ב C++ אפשר לשוב ולדמיין את הקוד הבא:

Vehicle *c = new Car();
Vehicle *d = new Boat();
Vehicle *p = new Plane();

cout << c.maxSpeed();
cout << d.maxSpeed();
cout << p.maxSpeed();

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

כדי לממש מבנה כזה בשפות מונחות עצמים משתמשים בירושה (Inheritance) או בממשקים (Interfaces).

2. המילה virtual

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

נתבונן בשתי דוגמאות C++ כדי לראות את ההבדל.

המימוש הבא עבור המחלקה Car וקוד ה main הנלווה ידפיסו 0 כשנפעיל את הפונקציה דרך המשתנה v וידפיסו 100 כשנפעיל אותה דרך המשתנה c:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

class Vehicle {
  public:
  int maxSpeed() {
    return 0;
  };
};

class Car : public Vehicle {
  public:
  int maxSpeed() {
      return 100;
  };
};

int main(int argc, char **argv)
{
  Vehicle *v = new Car();
  Car *c = new Car();

  cout << v->maxSpeed() << endl;
  cout << c->maxSpeed() << endl;

  delete v;
  delete c;
}

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

רק בהדפסה השניה C++ יודע שיש לו ביד אוביקט מסוג Car ולכן מפעיל את הפונקציה maxSpeed ממנה.

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

המילה השמורה virtual ב C++ אומרת לשפה שיש לחפש את הפונקציה בצורה דינמית בעת הקריאה אליה לפי סוג המידע האמיתי שנמצא במשתנה. המילה משפיעה על גודל קובץ התוכנית ובעצם מייצרת קובץ הפעלה קצת יותר גדול. בדוגמת הקוד שלנו קובץ ההפעלה ללא virtual היה בגודל 10,004 בתים אבל תוספת המילה virtual אצלי על המחשב הגדילה אותו ל 10,596 בתים. בתוכניות גדולות כמובן שההשפעה יותר מורגשת.

נראה איך התוכנית פועלת עם תוספת המילה virtual:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

class Vehicle {
  public:
  virtual int maxSpeed() {
    return 0;
  };
};

class Car : public Vehicle {
  public:
  virtual int maxSpeed() {
      return 100;
  };
};

int main(int argc, char **argv)
{
  Vehicle *v = new Car();
  Car *c = new Car();

  cout << v->maxSpeed() << endl;
  cout << c->maxSpeed() << endl;

  delete v;
  delete c;
}

עכשיו שתי שורות ההדפסה מדפיסות את אותו ערך: 100. כתיבת המילה virtual בתוך המחלקה Car לא הכרחית מאחר והיותה של פונקציה virtual היא תכונה שעוברת בירושה - אבל אנחנו בכל זאת נוהגים לכתוב אותה בכל ההיררכיה כדי להקל על אנשים שקוראים את הקוד.

בשפות Java ו C# שהמשיכו את C++ כבר ויתרו על האפשרות לפונקציה שאינה וירטואלית וכל הפונקציות הינן virtual בלי שתצטרכו לכתוב את זה.

3. פולימורפיזם בשפות דינמיות

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

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

class Car:
    def max_speed(self):
        return 100

class Plane:
    def max_speed(self):
        return 200

class Boat:
    def max_speed(self):
        return 150


c = Car()
p = Plane()
d = Boat()

print(c.max_speed())
print(d.max_speed())
print(p.max_speed())

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

'https://he.wikipedia.org/wiki/פולימורפיזם(מדעיהמחשב)