תכנות מונחה-עצמים – הבדלי גרסאות

מתוך ויקיפדיה, האנציקלופדיה החופשית
תוכן שנמחק תוכן שנוסף
מ הגהה: האיסוף הוא collection (האוסף הוא collector)
←‏ירושה: הוספת עוגן לצורך קישור מערך אחר (דף פירושונים: "ירושה")
שורה 82: שורה 82:


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



גרסה מ־16:26, 13 ביוני 2013

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

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

תכנות מונחה-עצמים היווה מהפכה בכתיבת תוכנה, ודרש מהמתכנתים התייחסות אחרת לפתרון בעיות ורכישת הרגלי תכנות חדשים. תכנות מונחה-עצמים הוא חלק מתפיסת פיתוח מונחית-עצמים, הכוללת גם ניתוח מערכות מונחה-עצמים (OOA), עיצוב מונחה-עצמים (OOD) ובמידה חלקית גם בסיסי נתונים מונחי-עצמים (Object-Oriented Databases).

רקע

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

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

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

היסטוריה

הרעיון של עצמים ומופעים שלהם הופיע לראשונה בשנת 1963 בתוכנה בשם Sketchpad.

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

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

בפיתוח תוכנה עסקית זכתה מתודולוגיה זו לתפוצה רחבה החל מסוף שנות ה־80, תחילה ב-Smalltalk ולאחר מכן בשפות התכנות C++‎ וטורבו פסקל, ולאחריהן בשפה Java. שפות נוספות התומכות בתכנות מונחה-עצמים הן פייתון, Ada‏ (בגרסת 95), Ruby ,Delphi ועוד רבות אחרות.

במסגרת יוזמת NET. של חברת מיקרוסופט פיתחה החברה שפה חדשה לתכנות מונחה-עצמים, #C, והשלימה יכולת זו שהייתה חלקית בגרסה ששית של שפת Visual Basic, בגרסה הקרויה Visual Basic .NET.

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

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

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

בכל מחלקה קיימות שתי פונקציות שמופעלות באופן אוטומטי על המופעים שלה בעת הופעתם ובעת מחיקתם. בעת יצירת המופע תופעל פונקציית בנאי (Constructor), באמצעותה אפשר לאתחל את האובייקט, לעתים באמצעות פרמטרים המועברים אליה. בעת הריסת האובייקט תופעל פונקציה הורסת (Destructor), באמצעותה אפשר למשל לשחרר זיכרון שהיה בשימוש על ידי העצם, או להרוס אובייקטים אחרים שכבר אין בהם צורך. בשפות המשתמשות במנגנון איסוף זבל (Garbage collection), כגון Java, ישנה פונקציה מקבילה אך היא נקראת רק בעת ההריסה הסופית של העצם על ידי המנגנון, ולכן תפקידיה שונים.

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

מאפיינים

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

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

עם זאת יש שמגדירים באופן שונה את המאפיינים[1]:

  • ריבוי ייצוגים (Multiple representations או Dynamic dispatch) - כאשר מבוצעת על אובייקט פעולה, האובייקט עצמו בוחר את המימוש שיבצע את הפעולה. זהו המאפיין העיקרי המבדיל בין אובייקטים לבין ADT - טיפוסי נתונים מופשטים.
  • מודולריות - הפרדה לתחומים שונים, וקיבוץ תכונות ושיטות ליחידות בעלות נושא או עניין אחד, אשר כל אחת מהן מהווה יחידה עצמאית וסגורה כמו בעולם הממשי.
  • הפשטת נתונים (Data Abstraction) הנעשית באמצעות כימוס (Encapsulation) והסתרת המידע (Information Hiding) - הסתרה של מימוש. ישנן שפות התומכות במנגנון אחר הנקרא Multi-methods.
  • היררכיה הממומשת באמצעות:
    • ירושה (Inheritance) - ירושה של מימוש של מתודות או משתנים. מחלקה היורשת ממחלקה אחרת מעניקה לאובייקטים שלה גם את המימוש הפנימי של המחלקה האחרת. כאשר מדובר בירושה ציבורית (כמו ברוב שפות התכנות), היא מגיעה בד-בבד עם הגדרה של המחלקה החדשה כתת-טיפוס של הראשונה.
    • פולימורפיזם של תת-טיפוסים (Subtyping) - טיפוס א' הוא תת-טיפוס של טיפוס ב' אם כל הממשק של ב' ממומש באובייקט א'. במקרה כזה, כל פעולה שניתן לבצע על אובייקט מטיפוס ב' ניתן לבצע גם על אובייקט מטיפוס א' - כולל העברה כפרמטר לפונקציות, או הפעלה של מתודות.
  • רקורסיה פתוחה (Open recursion) או חיפוש דינמי (Dynamic lookup) של מתודות. קיים משתנה מיוחד (או מילה שמורה) בשם this או self, המאפשר לגוף של מתודה לקרוא למתודה אחרת על אותו אובייקט. המזהה של המשתנה נכרך בזמן ריצה (Late binding)- מה שמאפשר למתודות של מחלקה אחת להפעיל מתודות שהוגדרו אחריה, במחלקה שירשה ממנה.

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

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

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

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

כימוס

ערך מורחב – כימוס

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

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

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

ישנן שפות תכנות מונחות-עצמים שאינן מאפשרות כימוס, או מאפשרות להפר אותו:

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

תת-טיפוסים

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

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

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

תמיכה לתת-טיפוסים בשפה מסייעת בשימוש בפולימורפיזם.

ירושה

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

ניתן להגדיר מחלקה חדשה על בסיס מחלקה קיימת. למחלקה החדשה ישנן כל התכונות והפעולות שירשה מהמחלקה שעל-פיה הוגדרה, ובנוסף ניתן להגדיר פעולות נוספות במחלקה החדשה, או לשנות פעולות שירשה. המחלקה המורישה קרויה מחלקת בסיס (base class), מחלקת אב (parent) או מחלקת-על (superclass). המחלקה היורשת קרויה מחלקה נגזרת (derived class), בן (child) או תת-מחלקה (subclass).

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

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

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

ראו גם

קישורים חיצוניים

הערות שוליים

  1. ^ ע"פ Benjamin C. Pierce, Types and Programming Languages עמ' 226-227, וע"פ על כוס קפה, מדריך לשפת C# עמ' 15-16