מחלקה כמנגנון הפשטה והפשטת עצמים: מה משותף ומה שונה?
מהות המחלקה כאמצעי להגדרה מרוכזת של מבנה והתנהגות קבוצת עצמים, מדוע מושג ה"מחלקה" חשוב ממושג ה"עצם" מבחינה הנדסית, סימולציית תנועת מכוניות, ופירוק זהות, התנהגות, מצב ומבנה (כולל דוגמת הקצה של מחסנית ב-++C עם מצביעים לפונקציות).
מודל מנטלי ועיקרון מנחה: לקודד את המשותף פעם אחת בלבד ולהחילו על כל העצמים!
בסימולציה של תנועת מכוניות בכבישים יש אלפי מכוניות בצבעים, גדלים ודפוסי נהיגה שונים, אך הדמיון ביניהן עולה בהרבה על השוני. המחלקה היא השרטוט ההנדסי שמגדיר פעם אחת בלבד את המבנה וההתנהגות הבסיסיים של מכונית ומחיל אותם על כולן!
בין עצמים מאותה מחלקה: הזהות (Identity) והמצב (State) שונים מעצם לעצם, בעוד שהפרוטוקול, מימוש ההתנהגות (המתודות) והמבנה (ההצהרות על משתני המופע) זהים לחלוטין!
אף על פי ש"עצם" הוא הישות המבצעת את החישוב בפועל, מושג ה"מחלקה" חשוב יותר מבחינה הנדסית — כי הוא מנגנון ההפשטה החשוב ביותר ב-OOP, המאפשר לשלוט במורכבות ולנהל אלפי עצמים תחת חוקיות אחת.
1. סעיף 10.1: מחלקה כמנגנון הפשטה — מדוע המחלקה חשובה יותר מהעצם עצמו?
הגדרת המבנה וההתנהגות במרוכז, ודוגמת סימולציית זרימת התנועה בכבישים
אנחנו נכנסים עכשיו אל חלק V: מחלקות, ופותחים במושג שעומד בלב ליבה של הנדסת התוכנה: המושג "מחלקה" (Class). בתכנות מונחה עצמים אנחנו מגדירים עצמים רבים שפועלים יחד במערכת. אבל איך אנחנו מגדירים אותם בלי לטבוע בים של קוד משוכפל? כאן בדיוק נכנסת המחלקה (Class): המחלקה היא בראש ובראשונה האמצעי שמשמש אותנו כדי להגדיר במרוכז את המבנה ואת ההתנהגות של קבוצת עצמים. המושג "מחלקה" הוא מושג מורכב ועשיר, ולמעשה זהו מנגנון ההפשטה החשוב ביותר בפרדיגמת תכנות מונחה עצמים!
שאלת חשיבה מהותית: מדוע מושג ה"מחלקה" חשוב יותר מן המושג "עצם" בתכנות מונחה עצמים?
תחשוב על זה רגע: הפרדיגמה כולה נקראת "תכנות מונחה עצמים" (Object-Oriented Programming), והעצם הוא הישות המוחשית שחיה בזיכרון ומבצעת את החישוב בפועל בזמן הריצה. ובכל זאת, מדוע מושג ה"מחלקה" חשוב יותר ממושג ה"עצם"?
כדי להרגיש את כוחה של המחלקה כמנגנון הפשטה, בוא נסתכל יחד על דוגמת סימולציית תנועת מכוניות בכבישים: נניח שאנחנו בונים סימולציה של זרימת התנועה בכבישים, ולצורך כך אנחנו נדרשים להגדיר מספר רב של עצמים המייצגים מכוניות. אף על פי שייתכן שלכל מכונית בודדת יהיה צבע שונה, גודל שונה, דפוסי נהיגה שונים וכיוצא באלו, יש לכולן הרבה מן המשותף — למשל המבנה הבסיסי של מכונית וההתנהגות הבסיסית של כלי רכב. במקרה הזה, הדמיון עולה בהרבה על השוני! הגדרת מחלקה מאפשרת לנו לקודד את המשותף פעם אחת בלבד ולהחילו באופן גורף על כל העצמים במערכת.
2. סעיף 10.2: הפשטת עצמים — מה משותף ומה שונה בין עצמים באותה מחלקה?
פירוק שלושת מאפייני היסוד של העצם (זהות, התנהגות, מצב ומבנה) ובחינת מקרה הקצה של ++C Stack
אנחנו כבר יודעים שעצם הוא ישות שיש לה זהות (Identity), התנהגות טיפוסית (Behavior) ומצב (State). עכשיו בוא נשאל שאלה מדויקת: כשאנחנו מסתכלים על קבוצת עצמים שמשתייכים לאותה מחלקה (למשל כל המכוניות בסימולציה שלנו), מה בדיוק משותף לכולם ומה שונה מעצם לעצם?
1. הזהות (Identity)
הזהות שונה במהותה בכל עצם ועצם, מבדילה אותו מכל שאר העצמים בעולם, והיא אינה יכולה להיות משותפת לעולם! גם שתי מכוניות מאותו דגם ובאותו צבע הן שני עצמים נפרדים בעלי זהות שונה.
2. ההתנהגות (Behavior): פרוטוקול ומימוש
ההתנהגות של כל העצמים מאותה מחלקה דומה ואף זהה בשני היבטים:
• הפרוטוקול (Protocol): אוסף ההודעות שהעצם מבין — זהה בכל העצמים של אותה מחלקה.
• מימוש ההתנהגות (Method Implementation): האופן שבו העצם מגיב בפועל להודעות (המתודות / קוד המתודות) — זהה גם הוא בכל העצמים שבאותה מחלקה.
3א. המצב (State)
המצב (הערכים הספציפיים השמורים בעצם ברגע נתון) של עצמים שונים הוא לרוב שונה מעצם לעצם — למשל, רכב אחד אדום ואחר כחול, רכב אחד בתנועה (נוסע) ואחר עומד (חונה).
3ב. המבנה (Structure)
לעומת המצב המשתנה, המבנה הפנימי של כל כלי הרכב בזיכרון הוא זהה לחלוטין — כלומר ההצהרות על משתני המופע זהות בכל העצמים הנוצרים מאותה מחלקה. את כל המשותף הזה אנו מגדירים במחלקה!
אתגר מחשבתי: האם ההתנהגות של כל העצמים מאותה מחלקה חייבת בהכרח להיות זהה?
למה "כן ולא"? ככלל, בשפות תכנות מונחות עצמים מקובל שכל העצמים של אותה מחלקה מתנהגים בהתאם למוגדר במחלקה. אבל זו תולדה של בחירה במנגנון הפשטה מסוים! בהחלט ייתכנו מנגנוני הפשטה אחרים שבהם ההתנהגות (המתודות) של עצמי המחלקה אינה נקבעת במחלקה באופן אחיד.
איך אפשר לבנות מחלקה שבה כל עצם בודד קובע לעצמו מימוש שונה של המתודות? זה אפשרי (אם כי לא סטנדרטי ואינו מומלץ). תסתכל על מימוש של מחסנית (Stack) בשפת ++C שבו במקום להגדיר מתודות רגילות במחלקה, אנחנו מגדירים את push ו-pop כמצביעים לפונקציות (Function Pointers):
enum { N = 100 };
int buff[N];
int size;
public:
void (*push)(int element);
int (*pop)(void);
};
במימוש הזה, אף שכל המחסניות חולקות את אותה המחלקה Stack ואת אותו מבנה שדות (buff ו-size), כל מופע בודד של Stack יכול להצביע במשתנים push ו-pop שלו על פונקציות שונות לחלוטין בזמן ריצה!
המחלקה Car כשרטוט מאחד מול מופעי המכוניות השונים בסימולציית התנועה
מיני-סימולציה 10.1: סייר המאפיינים של עצמי המחלקה — מה משותף ומה שונה?
מעבדת הפשטת עצמים: סימולציית המכוניות ומחסנית המצביעים לפונקציות ב-++C
🛠️ ייצור מכונית חדשה מהמחלקה Car:
שים לב: כל מכונית מקבלת זהות ייחודית ומצב ספציפי משלה, אך כולן חולקות בדיוק את אותו מבנה ואותה התנהגות שהוגדרו פעם אחת במחלקה!
⚡ שינוי התנהגות פר-עצם ב-class Stack (מצביעים לפונקציות):
לפנינו שני מופעים של אותה מחלקה Stack ב-++C (stackA ו-stackB). בחר לאיזו פונקציית push יצביע המצביע void (*push)(int element) של stackB:
📊 תוצאת הריצה על שני מופעי Stack:
הגשר הנרטיבי: הבנו מה המחלקה מפשטת – אבל אילו תפקידים נוספים היא ממלאת ומאילו 5 איברים פנימיים היא בנויה?
ראינו שהמחלקה מרכזת את המבנה, הפרוטוקול ומימוש ההתנהגות של קבוצת עצמים. אבל מעבר להיותה מנגנון הפשטה, למחלקה יש עוד שלושה תפקידים קריטיים במערכת (תבנית ליצירת עצמים עם new, הצהרה על פרוטוקול לבדיקות טיפוסים בזמן הידור, וישות עצמאית בזמן ריצה), והיא בנויה מחמישה רכיבי יסוד מדויקים: כור יצירה (Forge), בית-חרושת (Mill), פרוטוקול (Protocol), התנהגות (Behavior) ומבנה (Structure). בוא נעבור ליחידה הבאה ונפרק את האנטומיה של המחלקה לגורמים!
תפקידי המחלקה וחמשת רכיבי המבנה: Forge, Mill, Protocol, Behavior, Structure
שלושת התפקידים של המחלקה מעבר להפשטה (תבנית ליצירת עצמים עם new, הצהרה על פרוטוקול לאיתור שגיאות בהידור בשפות עם הטפסה סטטית, וישות בפני עצמה בזמן ריצה), וחמשת הרכיבים המרכיבים מחלקה שלמה.
מודל מנטלי ועיקרון מנחה: הבחנה חדה בין "חוזה/חתימה" לבין "מימוש בפועל"
חשוב על מפעל ייצור מתקדם: יש בו קטלוג הזמנה ליצירת מוצר חדש (Forge) ופס ייצור שבונה את המוצר בפועל (Mill); ולאחר שהמוצר נוצר, יש לו לוח כפתורים חיצוני (Protocol), מנוע פנימי שמבצע את הפעולות (Behavior) ושלדה פנימית (Structure)!
במבנה המחלקה אנו מפרידים תמיד בין החתימה (Signature) לבין המימוש (Implementation): ביצירת עצמים — Forge (חתימות הבנאים) מול Mill (מימוש הבנאים); ובהתנהגות העצמים — Protocol (חתימות המתודות) מול Behavior (מימוש המתודות)!
המחלקה ממלאת שלושה תפקידים נוספים מעבר להפשטה: (1) תבנית ליצירת עצמים בפקודת new; (2) הצהרה על פרוטוקול לבדיקות טיפוסים בזמן הידור; (3) ישות עצמאית המיוצגת לעיתים כעצם בזמן ריצה.
1. סעיף 10.3: שלושת תפקידי המחלקה (מעבר להיותה מנגנון הפשטה)
תבנית ליצירת עצמים (new), הצהרה על פרוטוקול עבור הטפסה סטטית, וישות בפני עצמה בזמן ריצה
ביחידה הקודמת ראינו שהתפקיד הראשון והבסיסי ביותר של המחלקה הוא לשמש כמנגנון הפשטה המאגד במרוכז את המבנה ואת ההתנהגות של קבוצת עצמים. אבל כשאנחנו מסתכלים על האופן שבו שפות תכנות מונחות עצמים פועלות בפועל, אנחנו מגלים שלמחלקה יש שלושה תפקידים מרכזיים נוספים מעבר להיותה מנגנון הפשטה:
1. תבנית ליצירת עצמים (Object Template)
בשפות תכנות רבות קיים ביטוי ייעודי — כגון new — שייעודו יצירת עצמים חדשים בזיכרון, והוא מקבל את שם המחלקה כפרמטר כדי לדעת איזה עצם לייצר וכיצד לבנות אותו.
2. הצהרה על פרוטוקול שהעצמים מבינים
המחלקה מגדירה את הטיפוס ואת אוסף ההודעות שהעצמים שלה מבינים. שפות שיש בהן הטפסה סטטית (טיפוסיות סטטית, כגון Java ו-++C) מסתמכות על הצהרה זו כדי לבצע בדיקות טיפוסים ולאתר שגיאות כבר בזמן הידור (Compile time)!
3. ישות בפני עצמה בזמן ריצה
המחלקה אינה רק טקסט שנעלם אחרי הקומפילציה — היא ישות שעומדת בפני עצמה, ובשפות מסוימות (ובפרט ב-Smalltalk, וכן דרך בבואה ב-Java) היא מיוצגת על ידי עצם חי בזמן ריצה!
2. סעיף 10.4: מבנה של מחלקה — חמשת רכיבי היסוד
פירוק מדויק של מחלקה שלמה לחמישה מרכיבים: כור יצירה (Forge), בית-חרושת (Mill), פרוטוקול (Protocol), התנהגות (Behavior) ומבנה (Structure)
עכשיו, כשאנחנו מבינים את כל התפקידים שהמחלקה ממלאת, בוא נעשה סדר מוחלט בשאלה: ממה בדיוק מורכבת מחלקה שלמה? כשאנחנו מנתחים מחלקה מתחת לזכוכית מגדלת הנדסית, אנחנו מגלים שהיא מורכבת מחמישה רכיבי יסוד מובחנים:
🔥 כור יצירה / מכרה עצמים (Forge)
אוסף ההודעות ליצירת עצמים חדשים — כלומר אוסף החתימות של הבנאים (Constructors Signatures) המגדירים אילו בקשות ליצירת עצם חדש ניתן להפנות למחלקה.
🏭 בית-חרושת לייצור עצמים (Mill)
המימוש של הבנאים (Constructors Implementation) — גוף הקוד והמנגנון שמקצה את הזיכרון, מאתחל את השדות ומייצר את העצם החדש בפועל.
📡 פרוטוקול (Protocol)
אוסף ההודעות שהעצמים מבינים — כלומר החתימה של המתודות (Method Signatures) המהוות את הממשק החיצוני של כל עצם מהמחלקה.
⚙️ התנהגות (Behavior)
המימוש של המתודות (Method Implementation) — גוף הקוד המורץ בפועל בתגובה לקבלת הודעה על ידי העצם.
📐 מבנה (Structure)
אוסף משתני המופע (Instance Variables) — מבנה הזיכרון והתכונות הפנימיות המרכיבים כל עצם שנוצר מהמחלקה.
חמשת רכיבי המבנה של מחלקה (10.4) ושלושת תפקידיה במערכת (10.3)
מיני-סימולציה 10.2: סורק הרנטגן של 5 רכיבי המחלקה בקוד חי
מעבדת שלושת תפקידי המחלקה (10.3): תבנית new, בדיקת טיפוסים בהידור וישות בזמן ריצה
בחר אחד משלושת התפקידים של המחלקה (מעבר להיותה מנגנון הפשטה) ובדוק כיצד הוא פועל מול פקודות חוקיות ושגויות:
הגשר הנרטיבי: הכרנו את כל 5 רכיבי המחלקה – אבל מה קורה כששפת תכנות מחליטה להשמיט חלק מהם?
מחלקה מלאה כוללת כור יצירה (Forge), בית-חרושת (Mill), פרוטוקול (Protocol), התנהגות (Behavior) ומבנה (Structure). אבל בעולם האמיתי של שפות התכנות ישנם מבנים מרתקים שכוללים רק תת-קבוצה מתוך חמשת הרכיבים האלה! ביחידה הבאה (סעיף 10.5) ננתח ארבע דוגמאות חשובות בשפות שונות: מנשק (Interface) ב-Java, מחלקה אנונימית (Anonymous Class) ב-Java, מחלקה מופשטת (Abstract Class) ב-++C, ומחלקה שלמה (`Point`) בשפת Smalltalk!
דוגמאות בשפות שונות: מנשק ומחלקה אנונימית ב-Java, מחלקה מופשטת ב-++C ומחלקת Point ב-Smalltalk
כיצד באים לידי ביטוי (או מושמטים) חמשת רכיבי המחלקה במנשק printable ב-Java, במחלקה אנונימית ב-Java, במחלקה מופשטת Shape ב-++C, ובמחלקה Point בסביבת Squeak ב-Smalltalk.
מודל מנטלי ועיקרון מנחה: כל מנגנון מיוחד בשפה הוא הפשטה שמדליקה או מכבה חלק מ-5 הרכיבים!
מנשק (Interface) משאיר רק את הפרוטוקול (ללא התנהגות, מבנה או יצירת עצמים). לעומתו, מחלקה אנונימית כוללת מבנה והתנהגות עבור מופע בודד, אך אינה מאפשרת ליצור עצמים נוספים!
מחלקה מופשטת (Abstract Class) כמו Shape (עם מתודה וירטואלית טהורה virtual void draw() = 0;) מאפשרת להגדיר בנאים, מתודות ומבנה, אך אינה מאפשרת ליצור עצמים חדשים ישירים מתוכה.
בסביבת Squeak המחלקה Point מגדירה מבנה (instanceVariableNames: 'x y' הנגישים רק מתוך המחלקה) והתנהגות ופרוטוקול (מתודות המופע setX:setY:, x ו-y).
1. סעיפים 10.5.1–10.5.3: מנשק ומחלקה אנונימית ב-Java, ומחלקה מופשטת ב-++C
בחינה של מבנים חלקיים שמשמיטים חלק מחמשת רכיבי המחלקה
עכשיו כשיש לנו ביד את רשימת חמשת הרכיבים של מחלקה שלמה (כור יצירה, בית-חרושת, פרוטוקול, התנהגות ומבנה), אנחנו יכולים להשתמש בהם כמו עדשת רנטגן כדי לנתח מבנים שונים בשפות תכנות ולהבין מיד במה הם דומים למחלקה מלאה ומה חסר בהם:
פרוטוקול טהור בלבד (interface)
מנשק בשפת Java מגדיר את הפרוטוקול (Protocol) אך לא את ההתנהגות (Behavior), אינו מגדיר מבנה שדות מופע, ואינו מאפשר ליצור עצמים חדשים (חסר Forge ו-Mill):
void print();
}
מופע יחיד ללא שכפול נוסף
מחלקה אנונימית מגדירה מבנה (private int s), אתחול חד-פעמי (s = 3), פרוטוקול והתנהגות (print()), אך אינה מאפשרת ליצור עצמים נוספים של המחלקה מעבר למופע הבודד p:
private int s;
{ s = 3; }
void print() {
System.out.println(s);
}
};
ללא יצירת עצמים ישירים
מחלקה מופשטת ב-++C (הכוללת מתודה וירטואלית טהורה = 0) אינה מאפשרת ליצור עצמים חדשים ישירים, אך ניתן להגדיר בה בנאים (עבור תת-מחלקות), מתודות ומבנה:
virtual void draw() = 0;
// pure virtual method
};
2. סעיף 10.5.4: מחלקה בסמולטוק — המחלקה Point בסביבת Squeak
הגדרת המבנה (instanceVariableNames: 'x y') והגדרת ההתנהגות והפרוטוקול (setX:setY:, x, y)
איך מוגדרת מחלקה מלאה בשפת Smalltalk? בסביבת העבודה Squeak, מחלקה מוגדרת באמצעות עורך מיוחד (ה-System Browser). בוא נתבונן בהגדרת המחלקה Point (נקודה דו-ממדית) ונראה כיצד מוגדרים בה המבנה וההתנהגות:
המחלקה מגדירה את המבנה של עצמים באמצעות רשימה מפורטת של שמות משתני-מופע (instanceVariableNames). במחלקה Point מוגדרים שני משתני מופע: x ו-y. משתנים אלה נגישים אך ורק מתוך קוד המחלקה עצמה!
instanceVariableNames: 'x y'
classVariableNames: ''
poolDictionaries: ''
category: 'Graphics-Primitives'
המתודה הראשונה מגדירה הודעה setX:setY: שלוקחת שני עצמים כארגומנטים ושומרת אותם במשתני המופע x ו-y. בנוסף מוגדרות ההודעות x ו-y שכאשר העצם מקבל אותן, הוא מחזיר (^) את העצם השמור במשתנה המופע המתאים:
x := xValue.
y := yValue
x
"Answer the x coordinate."
^x
y
"Answer the y coordinate."
^y
אילו מרכיבים ויכולות קיימים במנשק Java, במחלקה אנונימית, במחלקה מופשטת ב-++C ובמחלקת Smalltalk?
מיני-סימולציה 10.3: בוחן הרכיבים החסרים והקיימים ב-4 הדוגמאות של סעיף 10.5
מעבדת ה-System Browser ב-Squeak: המחלקה Point, כמוסת השדות ומתודות המופע
חקור את הגדרת המחלקה Point בסביבת Squeak ושלח הודעות אל מופע נקודה חי: נסה לעדכן את הקואורדינטות באמצעות setX:setY:, לקרוא אותן באמצעות ההודעות x ו-y, או לבדוק מה קורה כשמנסים לגשת ישירות למשתני המופע מבחוץ!
aPoint בזיכרון (Structure + State):
x = 15 , y = 30
הגשר הנרטיבי: ראינו איך מתודות המופע של Point פועלות – אבל מי בעצם מייצר את העצם החדש כשאנחנו שולחים הודעה למחלקה Point עצמה?
בסעיף 10.5.4 ראינו את מתודת המופע setX:setY: שמאתחלת נקודה קיימת. אבל איך נוצרת הנקודה מלכתחילה?
כאן אנחנו מגיעים אל הרעיון העמוק ביותר בפרק 10 (סעיף 10.6): מחלקה כעצם (Class as an Object)!
ביחידה הבאה נגלה כיצד ב-Smalltalk המחלקה Point עצמה היא עצם חי בזיכרון שמקבל את ההודעה x:y:, שולח לעצמו self basicNew ומאתחל את המופע באמצעות setX:setY:, ונשווה זאת למנגנון הבבואה / השתקפות (Reflection) בשפת Java (getClass(), getField(), set()) ולחידת השרשור o.getClass().getClass().getClass()!
מחלקה כעצם: מתודות מחלקה ב-Smalltalk מול מנגנון הבבואה (Reflection) ב-Java
איך המחלקה Point ב-Smalltalk פועלת כעצם המגיב להודעה x:y: עם self basicNew, כיצד מתשאלים מחלקות חיות ב-Squeak, ואיך פועל מנגנון ההשתקפות ב-Java (getClass, getField, set) כולל שרשור קריאות getClass().
מודל מנטלי ועיקרון מנחה: המחלקה לא נעלמת אחרי הקומפילציה — היא עצם חי בזיכרון!
בגישה מסורתית, המחלקה היא כמו תבנית אפייה שנזרקת לפח ברגע שהעוגיות מוכנות. בגישה המתקדמת של OOP, המחלקה היא "שף חי" שעומד במטבח בזמן הריצה — אפשר לשלוח לו הודעות, לבקש ממנו לייצר מופע חדש ולשאול אותו אילו שדות ומתודות יש לו!
ב-Smalltalk מחלקות הן עצמים לכל דבר (First-Class Objects). כאשר שולחים למחלקה Point את ההודעה x:y:, המשתנה self בתוך מתודת-המחלקה הוא עצם המחלקה Point עצמו, שיוצר מופע חדש (self basicNew) ומאתחלו ב-setX:setY:!
ב-Java מנגנון הבבואה (Reflection) מייצג כל מחלקה בזמן ריצה כעצם מטיפוס java.lang.Class. לכן o.getClass() מחזירה עצם, ושרשרת קריאות o.getClass().getClass()... מגיעה כבר בצעד השני לנקודת שבת: Class.class!
1. מחלקה כעצם בשפת Smalltalk — מתודת המחלקה x:y: ותשאול חי ב-Squeak
איך עצם המחלקה Point מקבל הודעות, מייצר מופע חדש עם basicNew ומאתחל אותו עם setX:setY:
בפרדיגמות תכנות מסורתיות, מחלקה נתפסת כישות שקיימת בזמן הידור בלבד (Compile-time construct) — מעין תבנית סטטית שנעלמת לאחר יצירת הקובץ הבינארי. לעומת זאת, בעיצוב מתקדם של תכנות מונחה עצמים, המחלקה עצמה קיימת בזמן ריצה כעצם חי בזיכרון! עיקרון זה בא לידי ביטוי בשתי השפות המרכזיות שלנו — באופן טהור ומובנה ב-Smalltalk, ובאמצעות מנגנון הבבואה/השתקפות (Reflection) ב-Java.
ב-Smalltalk, מחלקות הן בעצמן גם עצמים לכל דבר ועניין (First-Class Objects) המגיבים להודעות. הודעות שנשלחות ישירות אל עצם המחלקה נקראות מתודות מחלקה (Class Methods).
בוא נסתכל על מתודת-המחלקה המפורסמת של המחלקה Point ליצירת נקודה חדשה (Point class>>x:y:):
"Answer an instance of me with coordinates xInteger and yInteger."
^self basicNew setX: xInteger setY: yInteger
בוא נפרק יחד את הקסם שמתרחש בשורה הזו כאשר אנחנו שולחים למחלקה Point את ההודעה Point x: 10 y: 20:
self?
מכיוון שההודעה x: 10 y: 20 נשלחה ישירות אל המחלקה Point, המשתנה self בתוך מתודת-המחלקה הוא עצם המחלקה Point עצמו!
self basicNew?
המחלקה שולחת לעצמה את ההודעה basicNew (מתודה פרימיטיבית המוגדרת במחלקת התשתית Behavior), אשר מקצה מקום בזיכרון ויוצרת עצם חדש (מופע גולמי חדש של נקודה).
setX: xInteger setY: yInteger?
מיד לאחר ש-self basicNew החזיר את המופע החדש שנוצר, נשלחת אל המופע החדש הזה הודעת המופע setX:setY: (שראינו בסעיף הקודם!) כדי לאתחל את משתני המופע x ו-y שלו.
^?
המופע החדש והמאותחל של הנקודה מוחזר לשולח כתשובה להודעה!
🔎 תשאול וחקירת מחלקות בזמן אמת בסביבת Squeak
מאחר שב-Smalltalk המחלקה Point היא עצם חי בזיכרון, ניתן לפתוח עליה חלון חקירה (Point inspect) ולשלוח אליה הודעות תשאול בזמן אמת:
• Point subclasses — מחזיר מערך של כל תת-המחלקות היורשות מ-Point.
• Point allSuperclasses — מחזיר את שרשרת כל מחלקות-העל של Point.
• Point instVarNames — מחזיר את שמות משתני המופע המוגדרים במחלקה: #('x' 'y').
• Point selectors — מחזיר אוסף של כל שמות המתודות (הסלקטורים) שהמחלקה מבינה.
2. ייצוג מחלקה כעצם ב-Java (Reflection) ושרשרת getClass()
המתודה assignField עם getClass, getField ו-set, וניתוח נקודת השבת של o.getClass().getClass().getClass()
העיקרון שלמחלקות יש ייצוג מוחשי בזמן ריצה מצוי גם בשפת Java (באמצעות מנגנון הבבואה / ההשתקפות – Reflection, דרך אובייקטים מטיפוס java.lang.Class).
בשפת Java אפשר:
(1) לתשאל עצם בזמן ריצה לגבי מחלקתו באמצעות o.getClass();
(2) לתשאל את המחלקה בזמן ריצה לגבי שדה המוגדר בה באמצעות c.getField(fieldName);
(3) ולהציב בשדה זה ערך חדש בזמן ריצה באמצעות f.set(o, value)!
Class c = o.getClass();
Field f = c.getField(fieldName);
f.set(o, value);
}
אתגר חשיבה 1: האם getClass() ב-Java מחזירה עצם?
אם לא מוחזר עצם, כיצד ייתכן שניתן לקרוא למתודה getField()? ואם אכן מוחזר עצם, האם אפשר לקרוא למתודה getClass() שוב?
getClass() מחזירה עצם לכל דבר! העצם המוחזר הוא מופע של המחלקה java.lang.Class (שבעצמה יורשת מ-java.lang.Object).• העובדה שניתן להפעיל עליו את המתודה
c.getField(...) היא ההוכחה הישירה לכך שמדובר בעצם המממש פרוטוקול מתודות.• ומאחר שהערך המוחזר
c הוא עצם, בהחלט ניתן לקרוא עליו שוב למתודה getClass() (כלומר c.getClass())!
אתגר חשיבה 2: מה מחזירה שרשרת הקריאות o.getClass().getClass().getClass()...?
עבור עצם o של מחלקה כלשהי ב-Java, מה תחזיר הקריאה המשורשרת o.getClass().getClass().getClass() (ואף קריאה רביעית .getClass())?
o.getClass(): מחזיר את אובייקט המחלקה של o (למשל String.class — שהוא מופע מסוג Class).2.
o.getClass().getClass(): מהי המחלקה של אובייקט מסוג Class? המחלקה של כל אובייקט Class ב-Java היא המחלקה java.lang.Class עצמה (Class.class)!3.
o.getClass().getClass().getClass() (וכן קריאה רביעית והלאה): מהי המחלקה של Class.class? שוב java.lang.Class!💡 מסקנה: ב-Java מגיעים לנקודת שבת (Fixed Point / מעגל סגור) כבר ברמה השנייה! (בניגוד ל-Smalltalk שבה שרשרת שליחת ההודעה
class עוברת דרך מטה-מחלקות עד למעגל מחזורי בין Metaclass ל-Metaclass class).
יצירת נקודה ב-Smalltalk (Point x:y:) מול מנגנון הבבואה ונקודת השבת של getClass() ב-Java
מיני-סימולציה 10.4: מדגים שרשרת הקריאות o.getClass().getClass()...
מעבדת "מחלקה כעצם": Point class>>x:y: ב-Smalltalk מול assignField (Reflection) ב-Java
Point ב-Smalltalk:assignField(o, fieldName, value) ב-Java Reflection:הגשר הנרטיבי: השלמנו את כל סעיפי פרק 10 (10.1 עד 10.6) – בוא נסכם ונתרגל את כל מושגי הפרק!
עברנו יחד מסע שלם ומעמיק: מהגדרת המחלקה כמנגנון ההפשטה החשוב ביותר ב-OOP (10.1), דרך פירוק זהות, התנהגות, מצב ומבנה (10.2), שלושת תפקידי המחלקה וחמשת רכיבי המבנה שלה (10.3–10.4), מקרי המבחן בשפות השונות (10.5), ועד למחלקה כעצם חי בזמן ריצה ב-Smalltalk וב-Java (10.6). עכשיו נעבור ליחידה המסכמת של פרק 10 שבה נבחן את עצמנו בכרטיסיות זיכרון תלת-ממדיות (3D Flashcards) ונתכונן לפרק 11!
מילון מושגים אינטראקטיבי וסיכום פרק 10 (המושג "מחלקה")
חיבור כל חלקי הפאזל של פרק 10: מחלקה כמנגנון הפשטה, הפשטת עצמים, 3 תפקידי המחלקה, 5 רכיבי המבנה (Forge, Mill, Protocol, Behavior, Structure), דוגמאות בשפות שונות ומחלקה כעצם חי בזמן ריצה.
מודל מנטלי ועיקרון מנחה: שלוש עדשות ההסתכלות על המושג "מחלקה"
המחלקה היא מנגנון ההפשטה החשוב ביותר ב-OOP: היא מקודדת פעם אחת את מה שזהה בכל עצמיה (פרוטוקול, מימוש מתודות ומבנה משתני מופע), בעוד שהזהות והמצב נותרים ייחודיים לכל עצם.
למחלקה 3 תפקידים (תבנית new, חוזה לבדיקת טיפוסים בהידור, וישות בזמן ריצה) ו-5 רכיבים (Forge, Mill, Protocol, Behavior, Structure) שמופיעים במלואם או בחלקם במבנים שונים.
המחלקה מיוצגת כעצם חי בזמן ריצה — הן ב-Smalltalk (מתודת המחלקה x:y: עם self basicNew) והן ב-Java Reflection (getClass(), getField(), set() ונקודת השבת Class.class).
מבט-על על המושג "מחלקה" (סעיפים 10.1 עד 10.6)
מיני-סימולציה 10.5: בוחן אבחנה מהיר — בדוק את שלוש האבחנות הקריטיות של פרק 10!
כרטיסיות זיכרון תלת-ממדיות (3D Flashcards) לכל 12 מושגי פרק 10
לחץ על כל כרטיסייה כדי להפוך אותה ולבחון את עצמך על כל אחד מ-12 מושגי היסוד של פרק 10:
נחשפו: 0 / 12סיימנו במלואו את פרק 10 (המושג "מחלקה")! מה מחכה לנו בפרק 11?
בפרק 10 ניתחנו לעומק את המושג "מחלקה" כמנגנון ההפשטה החשוב ביותר בתכנות מונחה עצמים, פירקנו את חמשת רכיבי המבנה שלה, בחנו כיצד שפות שונות מממשות אותם וראינו כיצד מחלקה חיה כעצם בזמן ריצה.
Object (מתודות הדפסה printOn: ו-printString, מתודות שוויון וזהות =, == ו-hash, בדיקות שייכות למחלקה isKindOf:, isMemberOf: ו-respondsTo:, העתקה ושכפול copy, shallowCopy ו-postCopy, וטיפול בשגיאות subclassResponsibility, shouldNotImplement, error: ו-halt)!