תכנות מונחה-עצמים

תכנות מונחה־עצמים או לעיתים תכנות מוכוון־עצמים (באנגלית: Object-Oriented Programming, או בקיצור OOP) הוא פרדיגמת תכנות המשתמשת ב"עצמים" (אובייקטים) לשם תכנות תוכניות מחשב.
בפרדיגמה זו שמאפייניה הן מודולריות, מדרגיות ואבסטרקציה (הפשטה), מרחב התוכנה הוא למעשה מרחב של אובייקטים תכנותיים בעלי יחסים היררכיים ביניהם. כל ישות תכנותית היא מחלקה/אובייקט בעלת מאפיינים ופעולות משלה הקיימת כיחידה סגורה ועצמאית. בשיטה זו קיימת הפרדה בין המימוש הפנימי לבין הממשק החיצוני של המחלקה, בדומה לצורת העיצוב של מכשירים רבים.
הפרדיגמה מספקת למתכנת מספר דרכים לארגן ולפשט את קידוד התוכנה באמצעות כימוס (יצירת עצמים של "קופסאות שחורות"), הורשה (עצם Y יורש מעצם X ונגזרות תכונות מעצם לעצם) ופולימורפיזם (רב צורתיות שבה ניתן לשנות חלק מהתכונות הגזורות של העצם הנורש).
הפרדיגמה היוותה מהפכה בכתיבת תוכנה והחלה לשמש בפיתוח תוכנה החל מתחילת שנות ה־80 של המאה ה־20 ואילך, אך השימוש בפרדיגמה בשלמותה החל רק בשנות ה־90. רוב שפות התכנות המודרניות תומכות בתכנות מונחה־עצמים.
תכנות מונחה־עצמים הוא חלק מתפיסה (פרספציה) של פיתוח מונחה־עצמים, הכולל גם ניתוח מערכות מונחה־עצמים (OOA), עיצוב מונחה־עצמים (OOD) ובמידה חלקית גם בסיסי נתונים מונחי־עצמים (Object-Oriented Databases).

רקע

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

היסטוריה

הרעיון של עצמים ומופעים שלהם הופיע לראשונה בשנת 1963 בתוכנה בשם Sketchpad.
כמתודולוגיה לפיתוח תוכנה, מומש תכנות מונחה־עצמים לראשונה בשפת הסימולציה Simula 67, שנוצרה בשנות השישים על ידי הנורווגים אולה־יוהאן דאל וקריסטן ניגארד. בשנת 2001 הוענק לשניים פרס טיורינג, בזכות תרומתם לפיתוחו של תכנות מונחה־עצמים. השפה מימשה למעשה סוג פרימיטיבי של סגור, שהוא תכונה של שפות תכנות המאפשרת התנהגות דומה לזאת של אובייקט.
פיתוח ושכלול של הרעיון נעשו בשפת Smalltalk, שפותחה בשנות השבעים בזירוקס פארק. שפה זו זכתה להכרה נרחבת ולעניין רב באקדמיה ובתעשייה, והייתה השפה הראשונה שניתן לכנותה "מונחית־עצמים".
בפיתוח תוכנה עסקית זכתה מתודולוגיה זו לתפוצה רחבה החל מסוף שנות ה־80, תחילה ב־Smalltalk ולאחר מכן בשפות התכנות C++‎ וטורבו פסקל. שפת אייפל שהוכרזה ב־1985 הושפעה מהשפות פסקל, Simula, ‏Ada‏, Z ושימשה השראה לשפות פופולריות כמו C# ‏,Java ‏,ו־Ruby‏.
שפות נוספות התומכות בתכנות מונחה־עצמים הן פייתון, Ruby ,Delphi ועוד רבות אחרות.
במסגרת יוזמת NET. של חברת מיקרוסופט פיתחה החברה שפה חדשה לתכנות מונחה־עצמים, #C, והשלימה יכולת זו שהייתה חלקית בגרסה שישית של שפת Visual Basic, בגרסה הקרויה Visual Basic .NET.

מהות הפרדיגמה וטרמינולוגיה

מרכיב בסיסי בתכנות מונחה־עצמים הוא המחלקה (Class). מחלקה היא מבנה מופשט בעל שדות (fields) המגדירים את המחלקה כלפי פנים, תכונות (Properties) המגדירות ומאפיינות את המחלקה כלפי חוץ, ושיטות (Methods) שהן פונקציות ייחודיות למחלקה, הפועלות על משתני המחלקה. בחלק משפות התכנות (בעיקר המודרניות כמו Java ו־C#) קיימים גם אירועים (Events), שהם הודעות השייכות למחלקה ומוזנקות בהקשרים שונים, למשל בתחילתו או בסיומו של הליך או כתגובה לקלט מהמשתמש.
על מנת להשתמש במחלקה, המתכנת יוצר מופעים שלה. כל מופע של מחלקה הוא עצם (Object או Instance). היחס בין המחלקה לבין המופע, הוא היחס בין הגדרת טיפוס (Type) לבין הכרזת משתנה מסוג טיפוס בפועל בזיכרון המחשב. כלומר, בעוד שהמחלקה מהווה תבנית ומגדירה את התכונות והפעולות השייכות לה, עצם הוא משתנה פעיל שטיפוסו הוא המחלקה ומכיל את המידע הזה בפועל - כל עצם מכיל את התכונות והפעולות שהוגדרו במחלקה ממנה נוצר. לדוגמה, למחלקה הקרויה "מכונית" יש תכונות: צבע, דגם, שנת יצור, ופעולות: התנעה, נסיעה, עצירה. כל מכונית מסוימת היא עצם במחלקה זו, ולכל תכונה ערך המתאים למכונית המסוימת, למשל מזדה לבנה מודל 2003.
בכל מחלקה קיימות שתי פונקציות שמופעלות באופן אוטומטי על המופעים שלה בעת יצירתם ובעת מחיקתם. בעת יצירת המופע תופעל פונקציית בנאי (Constructor), באמצעותה אפשר לאתחל את האובייקט, לעיתים באמצעות פרמטרים המועברים אליה. בעת הריסת האובייקט תופעל פונקציה הורסת (Destructor), באמצעותה אפשר למשל לשחרר זיכרון שהיה בשימוש על ידי העצם, או למחוק אובייקטים אחרים שכבר אין בהם צורך. בשפות המשתמשות במנגנון איסוף זבל (Garbage collection), כגון Java, ישנה פונקציה מקבילה אך היא נקראת רק בעת ההריסה הסופית של העצם על ידי המנגנון, ולכן תפקידיה שונים.
התכנות במתודולוגיה זו מצריך תכנון ומאמץ יתר מבשיטה הרגילה, מפני שצריך לנתח, להגדיר ולארגן מראש את המערכת כולה ולבנות את כל המודל שלה, שבו יוגדרו כל האובייקטים השונים, הפונקציונליות שלהם וההיררכיה שביניהם, כך שהמתכנת הופך להיות ארכיטקט וקבלן גם יחד. הכתיבה אינה אינטואיטיבית כמו בשפה פרוצדורלית מכיוון שאפילו כתיבת פונקציה פשוטה, כופה יצירת אובייקט שלם עם פונקציות בונות והורסות. מצד שני נוצרת תוכנה מאורגנת, ולמתכנת המשתמש באובייקטים או המנסה לתחזק אותם, הרבה יותר קל להתמצא, לפתח ולתחזק כאשר הוא מתבסס על קוד של תכנות מונחה־עצמים, בהשוואה לקוד פרוצדורלי, מפני שהוא מאורגן וממופה יותר, ושינוי קטן במקום אחד בתוכנה אמור להשפיע באופן מקומי על האובייקט שבו הוא התבצע השינוי, ולא על כל התוכנה כולה.

הפשטת נתונים

בתכנות מונחה עצמים המתכנת יוצר מחלקות שמשמשות כעין "שחקנים" מופשטים, שיכולים לבצע עבודה, לדווח על מצבם ולתקשר עם אובייקטים אחרים. בשיטה זו ניתן לבצע הכללה של מקרים פרטיים. באמצעות שימוש בכימוס ניתן לבצע פולימורפיזם.
בדוגמה הבאה בשפת ג'אוה, נעשית הדגמה בהגדרה מופשטת מחלקת Animal, שיש לה את התכונות "מיקום", ו"מאגרי אנרגיה", ואת השיטות: "האם רעב", "לאכול", ו"לנוע".
public class Animal extends LivingThing{
     private Location loc;
     private double energyReserves;

     public boolean isHungry() {
         return energyReserves < 2.5;
     }
     public void eat(Food food) {
         // Consume food
         energyReserves += food.getCalories();
     }
     public void moveTo(Location location) {
         // Move to new location
         this.loc = location;
     }}
באמצעות ההגדרה שלמעלה של חיה ניתן ליצור אובייקטים כמו חזיר ופרה ולקרוא להן באופן הבא:
thePig = new Animal();theCow = new Animal();if (thePig.isHungry()) {
    thePig.eat(tableScraps);}if (theCow.isHungry()) {
    theCow.eat(grass);}theCow.moveTo(theBarn);
בדוגמה לעיל, המחלקה Animal היא הפשטה במקום מין של חיה בפועל, LivingThing היא הפשטה נוספת (במקרה זה הכללה) של Animal. ניתן לראות שבאמצעות הגדרה מופשטת של הטיפוס הכללי ניתן לבצע הכללה, ולהשתמש בה למספר רב של פרטי חיות, במקום לכתוב קוד נפרד לכל בעל חיים, דבר שמאפשר ליצור ריכוז של קוד. בנוסף, הפשטה כזו יכולה להסיר את הצורך שמתכנת היישום יעסוק בסוג המזון של כל חיה, ולתת למשתמש לעסוק בנתונים אלו. אם יש מאפיינים נפרדים לכל בעל חיים, למשל פרה שנותנת חלב בזמן שהיא חיה, לעומת חזיר שנותן בשר רק במותו, ניתן לאפיין כל סוג בנפרד באמצעות פולימורפיזם של תת־טיפוס.

תת־טיפוסים

הטיפוס של אובייקט הוא הממשק שלו. כלומר, אוסף ההודעות שהוא יודע לקבל, או אוסף המתודות הציבוריות שלו. המבנה הפנימי של האובייקט אינו חלק מהטיפוס שלו, כיוון שאינו משפיע על אוסף הפעולות שניתן לבצע איתו.
אם אובייקט יכול לבצע קבוצה של פעולות, הוא שייך גם לכל טיפוס שדורש רק חלק מהפעולות האלה. היכולת להתעלם מחלק מהממשק של אובייקט מאפשר לכתוב קטע קוד יחיד המאפשר לבצע מניפולציה על מגוון רחב של אובייקטים, תוך שהוא דורש רק את הממשק המשותף שלהם. תכונה זו מנוצלת במיוחד בשפות בעלת טיפוסיות ברווז כגון פייתון, Ruby, ו־Smalltalk. ממשקים (Interface) בשפת ג'אווה ומחלקות אבסטרקטיות בשפת ++C מאפשרים גם הם להגדיר במפורש תתי־טיפוסים, בלי צורך לבצע ירושה (ראו בהמשך) של המימוש.
ישנן שפות שאינן מונחות־עצמים התומכות בתתי טיפוסים - למשל שפת ML המאפשרת להעביר לפונקציה רשומה כלשהי, כל עוד היא מכילה לפחות את השדות שהפונקציה דורשת - גם אם היא מכילה שדות נוספים, וגם אם לא.
תמיכה לתת־טיפוסים בשפה מסייעת בשימוש בפולימורפיזם.

ריבוי ייצוגים

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