ירושה (תכנות)

מתוך המכלול, האנציקלופדיה היהודית
קפיצה לניווט קפיצה לחיפוש
Crystal Clear app help index.svg
ערך מחפש מקורות
רובו של ערך זה אינו כולל מקורות או הערות שוליים, וככל הנראה, הקיימים אינם מספקים.

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

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

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

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

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

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

שימושים

ירושה מאפשרת להשיג מספר מטרות:

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

דוגמת קוד

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

class Animal
{
    string name = "unknown animal";
}

class Cat : Animal
{
    Cat() {
        name = "Kitty Cat";
    }
}

class Bird : Animal
{
    Bird() {
        name = "Larry Bird";
    }
    
    int altitude = 0;   
    void fly() {
        altitude++;
    }
}

ירושה מרובה

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

פולימורפיזם והמרה

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

class SuperHero
{
    void useSpecialPower() {
        System.out.println("I am using my special power now");
    }
}

class Superman extends SuperHero
{
    void useSpecialPower() {
        System.out.println("I can fly and see through metals");
    }
}

class Main {
    void usePowers(SuperHero hero) {
        hero.useSpecialPower();
    }
	
    public static void main() {
        usePowers(new SuperHero());
        usePowers(new Superman());
    }
}

הקריאה האחרונה למתודה usePowers היא חוקית, כיוון שסופרמן הוא סוג-של גיבור על; מתוך הקוד של usePowers תיקרא המתודה המתאימה של סופרמן, ויודפס הטקסט המתאים עבור סופרמן. כל זאת, על אף שבמתודה זאת המשתנה hero מוגדר כמשתנה מטיפוס SuperHero ולא כמשתנה מטיפוס SuperMan.

היכולת לבצע השמה שבה עולים בעץ הירושה (מתייחסים למחלקה היורשת כמחלקה ממנה ירשה תכונות) נקראת Upcast, והיא בטוחה מבחינת מערכת הטיפוסים - דבר זה מובטח על ידי יחס הירושה. קיימת גם המרה הפוכה, כלומר מצב שבו יורדים בעץ הירושה. פעולה זו נקראת התכנות Downcast, והיא באופן כללי איננה "בטוחה" - לא ניתן לוודא נכונות שלה בזמן ההידור, ועל מנת למנוע התנהגות לא מוגדרת יש צורך לבצע בדיקה בזמן ריצה (או על ידי סביבת הריצה, כפי שקורה בשפות כגון #C או ג'אווה, או בדרכים אחרות).