פרק 8: מודל התכנות ב-Smalltalk • יחידה 8.1–8.2 חוקי היסוד של העצמים, משלוח הודעות וכמוסות

חמשת עקרונות מודל העצמים, כללים למשלוח הודעות וכמוסות מבוססת-עצם

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

📨
🧠

מודל מנטלי ועיקרון מנחה: הערכה מוקדמת מדויקת וכמוסות ברמת המופע הבודד

💡 האנלוגיה המוחשית

לשלוח הודעה ב-Smalltalk זה כמו לשלוח שליח עם חבילה: קודם מוודאים מי בדיוק הנמען (הערכת המקבל), אחר כך אורזים את כל המצרכים משמאל לימין (הערכת הארגומנטים), ורק אז השליח יוצא לדרך וחוזר עם התוצאה!

⚖️ חוק הברזל הפדגוגי

ב-Smalltalk הכמוסות היא מבוססת-עצם (Object-based) ולא מבוססת-מחלקה: עצם רשאי לגשת ישירות אך ורק למשתני המופע של עצמו (self). אפילו שני עצמים מאותה מחלקה אינם רשאים לגשת ישירות לשדות זה של זה!

🎯 תובנת המפתח

במתודה Rectangle>>containsRect:, המלבן הנוכחי ניגש ישירות לשדות origin ו-corner של עצמו, אך חייב לשלוח הודעות גישה (aRect origin) כדי לתשאל את המלבן השכן aRect!

1. חמשת עקרונות היסוד של מודל העצמים בשפת Smalltalk

חמשת החוקים הפשוטים שמהם נגזרת כל ההתנהגות של המערכת הטהורה

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

חוק 1 כל דבר הוא עצם (Everything is an object)

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

חוק 2 כל עצם הוא מופע של מחלקה (Every object is an instance of a class)

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

חוק 3 לכל מחלקה יש מחלקת-על (Every class has a superclass)

מבנה המחלקות מאורגן בעץ ירושה יחיד (Single Inheritance). בראש ההיררכיה ב-Squeak ניצבת המחלקה ProtoObject, שתחתיה יורשת מחלקת הבסיס Object.

חוק 4 חישוב מתבצע אך ורק ע"י שליחת הודעות (Everything happens by message sending)

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

חוק 5 איתור מתודות עוקב אחר שרשרת הירושה (Method lookup follows inheritance)

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

2. כללים למשלוח הודעות ואיתור עצמים חסרים במהדר

ארבעת השלבים הכרונולוגיים של כל שליחת הודעה ובדיקות התקינות של המהדר

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

1

הערכת מקבל ההודעה (Receiver Evaluation)

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

2

סדר הערכת הארגומנטים (Left-to-Right Arguments)

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

3

תזמון שליחת ההודעה (Message Dispatch Timing)

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

4

החלפת הביטוי בערך המוחזר (Expression Replacement)

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

🛡️ עצמים חסרים: איך המהדר (Compiler) שומר עלינו משגיאות מבנה?

כאשר אנחנו כותבים פקודת משלוח הודעה, המהדר (Compiler) של השפה מסייע לנו באיתור שגיאות תחביר בסיסיות ומתריע כאשר חסרים עצמים הנדרשים לביצוע התקין של ההודעה:
• במקרה של הודעה אונרית (Unary Message): המהדר יבדוק שקיים עצם מקבל ושם להודעה.
• במקרה של הודעת מילות-מפתח (Keyword Message): המהדר יוודא בנוסף למקבל, שלא חסרים ארגומנטים כלשהם הנדרשים למילות המפתח.

3. משתני מופע, מתודות וכמוסות מבוססת-עצם (Encapsulation)

למה ב-Smalltalk מופע אינו יכול לגשת ישירות לשדות של מופע אחר מאותה מחלקה? ניתוח Rectangle>>containsRect:

כעת בוא נבחן כיצד מחלקה מגדירה את המבנה (משתני מופע – Instance Variables) ואת ההתנהגות (מתודות – Methods) של העצמים שלה, ונגלה הבדל מרתק ומחמיר בין הכמוסות של Smalltalk לבין שפות כמו Java או ++C:

☕ ב-Java ו-++C: כמוסות מבוססת-מחלקה (Class-based)

בשפות כמו Java ו-++C, הכמוסות מוגדרת ברמת המחלקה: מופע אחד של מחלקה (למשל מלבן א') רשאי לגשת ישירות לשדות הפרטיים (private) של מופע אחר מאותה המחלקה (מלבן ב', כגון otherRect.origin).

💎 ב-Smalltalk: כמוסות מבוססת-עצם בלבד (Object-based)

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

בוא נראה את דוגמת הבוחן המאלפת מתוך המחלקה Rectangle — המתודה containsRect: הבודקת האם המלבן הזר aRect מוכל בתוך המלבן הנוכחי:

Rectangle >> containsRect: aRect
    "Answer whether aRect is within the receiver (OK to coincide)."
    ^ aRect origin >= origin and: [ aRect corner <= corner ]
🔍 1. ניתוח הכמוסות בקוד

המשתנים origin ו-corner מימין לאופרטורים הם משתני המופע של המלבן הנוכחי (self), והגישה אליהם ישירה. לעומת זאת, כדי לקבל את הנקודות של המלבן הזר aRect, המלבן הנוכחי חייב לשלוח הודעות גישה מפורשות: aRect origin ו-aRect corner (מתודות גישה – Accessor methods).

⚖️ 2. תלות מול ביצועים (Accessors)

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

🔐 3. הרשאות של משתנים ומתודות

• משתני מופע בירושה: בניגוד ל-Java/++C, לא ניתן להסתיר משתני מופע ממחלקות יורשות — הם תמיד נגישים למחלקות-הבנות (בדומה ל-protected).
• מתודות ופרוטוקולים: כל המתודות הן פומביות (public). נהוג לחלקן לקטגוריות לוגיות הנקראות פרוטוקולים (כגון initialization או accessing). לסימון מתודה כפרטית נהוג לשייכה לפרוטוקול private ולהוסיף קידומת pvt לשמה.

עזר גרפי 8.1 • סדר הערכת הודעה וכמוסות מבוססת-עצם

ארבעת שלבי משלוח הודעה וגבול הכמוסות ב-Rectangle>>containsRect:

Message Evaluation & Encapsulation
סדר הערכת משלוח הודעה (מימין לשמאל בתרשים): שלב 1: הערכת המקבל העצם המקבל (Receiver) מוערך תמיד ראשון! ⟵ שלב 2: הערכת ארגומנטים הארגומנטים מוערכים לפי הסדר משמאל לימין ⟵ שלב 3: שליחת ההודעה נשלחת למקבל רק לאחר הערכת המקבל והארגומנטים ⟵ שלב 4: החלפת הביטוי הערך המוחזר מחליף את הביטוי המקורי בקוד העצם המקבל (self : Rectangle) גישה ישירה ל-origin שדה פנימי של self גישה ישירה ל-corner שדה פנימי של self ✓ מותר לגשת ישירות למשתני המופע של עצמי בלבד! מופע אחר מאותה מחלקה (aRect : Rectangle) הודעת גישה: aRect origin Accessor Method (פומבית) הודעת גישה: aRect corner Accessor Method (פומבית) 🔒 השדות הפנימיים של aRect חסומים לגישה ישירה!
🛡️

מיני-סימולציה 8.1: בודק המהדר (עצמים חסרים) ומוסכמות פרוטוקולים

לחץ על כל ביטוי כדי לבדוק כיצד המהדר והסביבה מנתחים אותו
⏱️
סימולציה אינטראקטיבית גדולה 8.1

מעבדת ציר הזמן של משלוח הודעות ובוחן הכמוסות של Rectangle>>containsRect:

▶️ סימולטור צעד-אחר-צעד: הערכת ביטוי משלוח הודעה
שלב 1 מתוך 5
🔬 מעבדת הכמוסות ב-Rectangle>>containsRect:

בחר כיצד לגשת לשדות של המלבן הנוכחי (self) ושל המלבן השכן (aRect) וראה מה מותר ומה נחסם:

⚡

הגשר הנרטיבי: אם כל דבר ב-Smalltalk הוא עצם – אז מהי בעצם המחלקה עצמה, ואיך יוצרים ממנה מופעים?

למדנו את הכלל הראשון: "כל דבר הוא עצם". אבל רגע — אם כל דבר הוא עצם, זה אומר שגם המחלקה עצמה (כמו Point, Color או Dog) היא עצם חי בזמן ריצה! אז מי המחלקה שיצרה את עצם המחלקה? מהי מטה-מחלקה (Metaclass)? ומהו ההבדל המהפכני של משתני מופע של מחלקה (Class Instance Variables) שמאפשר לכלב (Dog) ולצבוע (Hyena) להחזיק מונים נפרדים לחלוטין ולממש את תבנית היחידן (Singleton)? בוא נעבור ליחידה 8.3!

סוף יחידה 8.1–8.2 • עקרונות, משלוח הודעות וכמוסות
פרק 8: מודל התכנות ב-Smalltalk • יחידה 8.3 המחלקה כעצם חי, מטה-מחלקות ותבנית היחידן

מחלקות כעצמים, מטה-מחלקות (Metaclasses), משתני מופע של מחלקה ותבנית יחידן (Singleton)

תשאול עצמים באמצעות ההודעה class, המטה-מחלקה שמאחורי הקלעים, מתודות מחלקה כבתי חרושת דינמיים, עקרון העותק הנפרד בדוגמת הכלב והצבוע (Dog ו-Hyena), ומימוש אלגנטי של Singleton עם self new.

🏛️
🧠

מודל מנטלי ועיקרון מנחה: המחלקה היא עצם חי שלכל תת-מחלקה שלו יש עותק פרטי של משתני המופע של המחלקה!

💡 האנלוגיה המוחשית

חשוב על מחלקה כמו על "בית חרושת" שמייצר מכוניות. ב-Smalltalk, גם בית החרושת עצמו הוא עצם חי שיש לו מנהל משלו (המטה-מחלקה) ומונה ייצור פרטי על הקיר שלו!

⚖️ חוק הברזל הפדגוגי

כאשר מחלקה מגדירה משתנה מופע של מחלקה (Class Instance Variable), כל תת-מחלקה שיורשת ממנה מקבלת עותק נפרד, פרטי ועצמאי משלה של אותו משתנה (כמו המונה count ב-Dog וב-Hyena).

🎯 תובנת המפתח

במימוש תבנית היחידן (WebServer class>>uniqueInstance), חובה להשתמש בביטוי הדינמי self new (ולא ב-WebServer new), כדי שגם תת-מחלקות יוכלו לרשת את התבנית וליצור יחידן משל עצמן!

1. תשאול עצמים (class), מטה-מחלקות (Metaclasses) ומתודות מחלקה

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

המחלקה היא הלב הפועם המגדיר את המבנה ואת ההתנהגות של קבוצת עצמים. מאחר שכל עצם ב-Smalltalk יודע בכל רגע מאיזו מחלקה הוא נוצר, ניתן לתשאל כל עצם בזמן ריצה לגבי המחלקה שיצרה אותו על ידי שליחת ההודעה האונרית class אליו:
• משלוח הביטוי 1 class יחזיר את עצם המחלקה SmallInteger.
• משלוח הביטוי (4@5) class יחזיר את עצם המחלקה Point.

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

🏛️ תפקיד המטה-מחלקה וכפתור ה-class ב-Browser

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

🏭 מתודות מחלקה (Class Methods) מול static ב-Java/++C

תפקידן העיקרי של מתודות מחלקה הוא לשמש כתבניות ("בתי חרושת" – Factories) ליצירת מופעים, כגון ההודעה new, או מתודות ליצירת מופעים עם אתחול ספציפי כמו Color blue (שליחת ההודעה blue למחלקה Color המחזירה מופע מוכן בצבע כחול) או Point x: 10 y: 20.
בשונה ממתודות סטטיות (static) ב-Java או ++C המנותקות ממודל העצמים, ב-Smalltalk אלו הודעות אמיתיות הנשלחות אל עצם המחלקה בזמן ריצה, עוברות קישור דינמי מלא ויכולות לעבור ירושה ודריסה!

2. משתני מופע של מחלקה (Class Instance Variables): דוגמת הכלב והצבוע (Dog ו-Hyena)

עקרון העותק הנפרד והמבודד לכל מחלקה בעץ הירושה

מעבר למתודות מחלקה, המטה-מחלקה מאפשרת להגדיר גם משתנים השייכים לעצם המחלקה עצמו — הנקראים משתני מופע של מחלקה (Class Instance Variables). הם מוגדרים בצד המחלקה באמצעות השדה instanceVariableNames: של המטה-מחלקה (למשל Dog class instanceVariableNames: 'count'), ושמם מתחיל באות קטנה.

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

🐕 המחשה לעקרון העותק הנפרד: מחלקת הכלבים (Dog) ותת-מחלקת הצבועים (Hyena) Isolated Class Instance Variable: count
"הגדרת משתנה מופע של מחלקה ב-Dog:"
Dog class instanceVariableNames: 'count'

Dog class >> initialize
    count := 0.

Dog class >> new
    count := count + 1.
    ^ super new
"Hyena יורשת מ-Dog ומקבלת עותק count נפרד!"
Dog subclass: #Hyena ...

Dog initialize.   "מאפס רק את count של Dog"
Hyena initialize. "מאפס בנפרד את count של Hyena"
Dog new.        "מקדם רק את מונה הכלבים!"
Hyena new.      "מקדם רק את מונה הצבועים!"

שים לב: אפילו ש-Hyena new מפעילה את מתודת המחלקה new שהיא ירשה מ-Dog, הגישה למשתנה count בתוך המתודה פונה אל העותק הפנימי והמבודד של Hyena! המונים של הכלבים והצבועים מבודדים לחלוטין ואינם משפיעים זה על זה.

3. תבנית עיצוב יחידן (Singleton Pattern): הקסם של self new

איך מבטיחים מופע יחיד למחלקה (כגון WebServer או שעון) ומדוע אסור לקבע את שם המחלקה בקוד?

יישום הנדסי קלאסי ואלגנטי למשתנה מופע של מחלקה הוא מימוש תבנית היחידן (Singleton Pattern) — כאשר אנו מעוניינים להבטיח שלמחלקה מסוימת (למשל שרת אינטרנט WebServer או שעון מערכת) ייווצר אך ורק מופע אחד ויחיד בכל זמן הריצה של המערכת.

כיצד זה פועל מבעד למנוע?
1. מגדירים משתנה מופע של מחלקה בשם uniqueInstance במחלקה WebServer.
2. כותבים מתודת מחלקה באותו השם המבצעת בדיקת קיום ואתחול עצל (Lazy Initialization): אם המשתנה ריק (nil), מאתחלים אותו ע"י פנייה ל-self new, ולבסוף מחזירים אותו:

WebServer class >> uniqueInstance
    uniqueInstance ifNil: [ uniqueInstance := self new ].
    ^ uniqueInstance

💎 שאלת מיליון הדולר: למה כתבנו self new ולא WebServer new?

זוהי תובנה קריטית שמחברת בין הקישור הדינמי לבין עקרון העותק הנפרד של משתנה מופע של מחלקה! המשתנה self בתוך מתודת מחלקה מתייחס תמיד לעצם המחלקה הדינמי שקיבל את ההודעה בזמן ריצה. אם מחר ניצור מחלקה-בת של WebServer (למשל SecureWebServer), היא תירש אוטומטית עותק עצמאי משלה של המשתנה uniqueInstance ואת מתודת המחלקה uniqueInstance. כשנשלח SecureWebServer uniqueInstance, הביטוי self new ייצור מופע מטיפוס המחלקה-הבת (SecureWebServer) וישמור אותו בעותק ה-uniqueInstance הפרטי שלה! אילו היינו מקבעים בקוד WebServer new, היינו פוגעים ביכולת הירושה ומחזירים תמיד שרת רגיל.

עזר גרפי 8.2 • מטה-מחלקות ועקרון העותק הנפרד

היררכיית המופע, עצם המחלקה והמטה-מחלקה + בידוד המונה count ב-Dog וב-Hyena

Metaclasses & Singleton
שרשרת המופעים (כל מחלקה היא עצם!): המטה-מחלקה (למשל Point class / Dog class) מגדירה מתודות מחלקה (new) ומשתני מופע של מחלקה ↑ מופע של (class) עצם המחלקה (למשל Point / Color / Dog) מקבל הודעות יצירה (Point x: 10 y: 20, Color blue) ↑ מופע של ((4@5) class) המופע הקונקרטי (למשל 4@5 או מופע של כלב) עקרון העותק הנפרד (משתני מופע של מחלקה): 🐕 עצם המחלקה Dog עותק פרטי: count Dog new ➔ +1 Dog initialize מאפס רק את המונה של Dog! 🐾 תת-המחלקה Hyena עותק נפרד: count Hyena new ➔ +1 Hyena initialize מאפס רק את המונה של Hyena!
🔀

מיני-סימולציה 8.2: למי נשלחת ההודעה? (עצם המחלקה מול מופע קונקרטי)

בחר ביטוי קוד וראה מי המקבל ואיפה מוגדרת המתודה
🐕
סימולציה אינטראקטיבית גדולה 8.2

מעבדת העותק הנפרד (Dog מול Hyena) וסימולטור ה-Singleton בירושה

🐾 ניסוי 1: הרץ הודעות על עצמי המחלקות Dog ו-Hyena
עצם המחלקה Dog
count = 0
תת-המחלקה Hyena
count = 0
🌐 ניסוי 2: תבנית היחידן (WebServer uniqueInstance)
⚡

הגשר הנרטיבי: הבנו את כוחן של המחלקות והמטה-מחלקות – אבל איך בנוי עץ הירושה כולו ואיך משתפים קוד ללא ירושה מרובה?

ראינו שכל מחלקה יורשת ממחלקת-על. אבל מי ניצבת בראש הפירמידה כולה ב-Squeak (Object ו-ProtoObject)? איך מגדירים מחלקות ומתודות מופשטות בשפה שבה אין מילת מפתח abstract (רמז: self subclassResponsibility)? ואיך פותרים את מגבלת הירושה היחידה באמצעות מנגנון ה-Traits? בוא נמשיך ליחידה 8.4!

פרק 8: מודל התכנות ב-Smalltalk • יחידה 8.4 היררכיית הירושה, הפשטה ו-Traits

מחלקות-על (Superclasses), מחלקות ומתודות מופשטות (subclassResponsibility), תבנית Template Method ו-Traits

ירושה יחידה משורש העץ (ProtoObject ו-Object), אכיפת מימוש במתודות מופשטות בשרשרת המספרים (Magnitude → Number → Integer → SmallInteger), מתודות היברידיות (max:), ושיתוף קוד באמצעות Traits (TAuthor ב-SBEGame).

🌳
🧠

מודל מנטלי ועיקרון מנחה: ירושה יחידה אנכית (Is-A) + שאיבת התנהגות אופקית באמצעות Traits

💡 האנלוגיה המוחשית

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

⚖️ חוק הברזל הפדגוגי

ב-Smalltalk אין מילת מפתח שמורה abstract! מתודה הופכת למופשטת על ידי שליחת ההודעה self subclassResponsibility בגופה, שתזרוק שגיאת ריצה אם תת-המחלקה לא תדרוס אותה.

🎯 תובנת המפתח

כדי להתגבר על מגבלת הירושה היחידה מבלי להסתבך בירושה מרובה, מנגנון ה-Traits מאפשר "לשאוב" חבילות של מתודות (ללא משתני מופע) לתוך כל מחלקה בעזרת uses:!

1. מבנה עץ הירושה היחידה: Object ו-ProtoObject

למה לכל מחלקה יש הורה אחד בלבד, ומי ניצבת בראש הפירמידה בסביבת Squeak?

הארכיטקטורה של Smalltalk תומכת בירושה יחידה (Single Inheritance) בלבד — כלומר, לכל מחלקה יש מחלקת-הורה (Superclass) אחת בלבד שממנה היא יורשת את המבנה (משתני המופע) ואת ההתנהגות (המתודות). עץ ההיררכיה כולו מתנקז למחלקת שורש אחת:

  • בגרסאות קלאסיות של השפה, מחלקת האם שבראש ההיררכיה נקראת Object, ורובן המוחלט של המחלקות יורשות ממנה את השירותים המתקדמים שכל עצם צריך.
  • בסביבת Squeak הוכנסה מעל Object מחלקת-על עליונה בשם ProtoObject, הממוקמת בשורש הקיצוני של העץ ומספקת את הסט המינימלי והבסיסי ביותר של פעולות והודעות שכל עצם חייב להבין.
  • כל מחלקה חדשה נוצרת על ידי שליחת הודעה אל מחלקת-העל שלה: subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:.
  • לחיצה על כפתור ה-hierarchy בדפדפן המערכת (System Browser) פותחת את ה-Hierarchy Browser המציג את העץ ההיררכי המלא ואת הורשת משתני המופע והמתודות מטה.

2. מחלקות ומתודות מופשטות (subclassResponsibility) ותבנית Template Method

איך מגדירים מתודה מופשטת ללא מילה שמורה abstract, ואיך Magnitude מעניקה מתודות שלמות בחינם?

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

ב-Smalltalk אין מילת מפתח תחבירית שמורה כגון abstract! כיצד אם כן מסמנים מתודה כמופשטת? פשוט על ידי שליחת ההודעה המיוחדת self subclassResponsibility בתוך גוף המתודה! הצהרה זו מעבירה מפורשות את חובת המימוש למחלקה-הבת. אם תיווצר מחלקה-בת שלא תדרוס (Override) את המתודה ותנסה להריץ אותה, הפקודה subclassResponsibility תזרוק שגיאת ריצה קריטית המעידה כי חובת המימוש חלה על תת-המחלקה.

"דוגמה 1: מתודה מופשטת לכפל ב-Number"
Number >> * aNumber
    "Answer the result of multiplying the receiver by aNumber."
    self subclassResponsibility
"דוגמה 2: מתודה היברידית (Template Method) ב-Magnitude"
Magnitude >> max: anotherMag
    self < anotherMag
        ifTrue: [ ^ anotherMag ]
        ifFalse: [ ^ self ]

🔢 שרשרת ההפשטה של המספרים

סדר הירושה מהשורש: Magnitude (מופשטת – גדלים ברי-השוואה) מורישה ל-Number (מופשטת – מגדירה בין היתר את הכפל * כמתודה מופשטת), המורישה ל-Integer (מופשטת בעצמה), המורישה ל-SmallInteger שהיא כבר מחלקה קונקרטית בתחתית העץ הדורסת ומממשת בפועל את פעולות הכפל בחומרה!

🧩 מתודות היברידיות / תבנית Template Method

במחלקה המופשטת Magnitude, המתודה max: ממומשת במלואה! היא נשענת על קריאה למתודה המופשטת <. ברגע שתת-מחלקה קונקרטית (כגון Date, Time או Number) מממשת רק את המתודה הבסיסית <, היא מקבלת בחינם את המימוש השלם של max:, min:, >=, <= ו->!

3. מאפיינים שיתופיים (Traits): שיתוף התנהגות ללא ירושה מרובה

איך שואבים מקבצי מתודות (כגון TAuthor) לתוך מחלקה (כגון SBEGame) מבלי לשנות את עץ הירושה שלה?

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

1. הגדרת Trait חדש: Trait named: #TAuthor
    uses: { }
    category: 'SBE-Quinto'
2. הוספת מתודה ל-Trait: TAuthor >> author
    "Returns author initials"
    ^ 'on'
3. שאיבה למחלקה עם uses:: BorderedMorph subclass: #SBEGame
    uses: TAuthor
    instanceVariableNames: 'cells'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'SBE-Quinto'

ברגע שהוספנו את מילת המפתח uses: TAuthor בהכרזת המחלקה SBEGame (שיורשת מ-BorderedMorph), כל מופע של SBEGame תומך ישירות במתודה author (הביטוי SBEGame new author מחזיר 'on'), וזאת מבלי לשנות כלל את עץ הירושה המקורי שלה!

עזר גרפי 8.3 • עץ הירושה המופשטת מול הרכבת Traits

היררכיית המספרים (Magnitude עד SmallInteger) ושאיבת TAuthor לתוך SBEGame

Is-A vs. Has-Behavior (Traits)
שרשרת הירושה והמתודות המופשטות: ProtoObject ➔ Object (שורש העץ ב-Squeak) Magnitude (מופשטת: מממשת max:, דורשת <) Template Method Pattern Number (מופשטת: * שולחת self subclassResponsibility) מעבירה את האחריות לתת-המחלקות Integer (מחלקה מופשטת למספרים שלמים) ✓ SmallInteger (מחלקה קונקרטית: מממשת * ו-< בפועל!) ירושה יחידה (Is-A) + שאיבת Trait (uses:): BorderedMorph מחלקת-העל הישירה (Superclass) subclass: (ירושה) 🧩 Trait: TAuthor author >> ^ 'on' (ללא משתני מופע) uses: SBEGame instanceVariableNames: 'cells' SBEGame new author ➔ 'on'
🔬

מיני-סימולציה 8.3: חוקר מתודות מופשטות (subclassResponsibility) ומתודות היברידיות

לחץ על כל מנגנון כדי לראות כיצד הוא פועל בזמן ריצה
🧪
סימולציה אינטראקטיבית גדולה 8.3

מעבדת מחלקות מופשטות והרכבת Traits (ניסוי כפל ב-Number מול SmallInteger ושאיבת TAuthor)

🔢 ניסוי 1: מה קורה כשמפעילים * או max: בהיררכיית Magnitude?
🧩 ניסוי 2: הרכבת ה-Trait TAuthor במחלקה SBEGame
⚡

הגשר הנרטיבי: ראינו איך העץ ההיררכי בנוי – אבל איך מערכת זמן הריצה מטפסת בו כדי לאתר מתודה, ומה ההבדל בין self ל-super?

כעת אנחנו מוכנים לצלול אל הלב הדינמי של השפה (סעיפים 8.5–8.6): איך פועל אלגוריתם איתור המתודות (Method Lookup) במעלה עץ הירושה? מה קורה כשהחיפוש מגיע עד ל-ProtoObject ולא מוצא את המתודה (רמז: doesNotUnderstand:)? ואיך טעות של מילה אחת בין self ל-super בתוך מתודת initialize מכניסה את המערכת ללולאה אינסופית? בוא נעבור ליחידה 8.5–8.6!

פרק 8: מודל התכנות ב-Smalltalk • יחידה 8.5–8.6 המנוע הדינמי של השפה

הקישור הדינמי, איתור מתודות (Method Lookup), דריסה והרחבה, self מול super ו-doesNotUnderstand:

אלגוריתם החיפוש במילון המתודות (methodDict) במעלה עץ הירושה עד ProtoObject, מנגנון doesNotUnderstand:, ערך החזרה ממתודה (^ מול ברירת המחדל self), דריסה (defaultColor) מול הרחבה (super initialize), ו-5 צבעי כפתור ה-inheritance.

🔍
🧠

מודל מנטלי ועיקרון מנחה: גם self וגם super מצביעים על אותו עצם בדיוק — ההבדל הוא רק מאיפה מתחיל חיפוש המתודה!

💡 האנלוגיה המוחשית

דמיין בניין קומות משפחתי: בקריאה ל-self החיפוש תמיד מתחיל בקומת הקרקע של הדייר עצמו (מחלקת המופע הדינמית). בקריאה ל-super אנחנו לוקחים מעלית ישירות לקומה שמעל הקומה שבה כתוב הקוד הנוכחי!

⚖️ חוק הברזל הפדגוגי

במערכת זמן הריצה מתקיים תמיד self == super (שניהם מייצגים את העצם המקורי שקיבל את ההודעה). אולם self מתחיל את החיפוש במחלקת המופע הדינמית, ואילו super מתחיל במחלקת-העל של המחלקה שבה נכתב הקוד!

🎯 תובנת המפתח

אם מתודה מסתיימת ללא חץ החזרה ^, היא מחזירה אוטומטית את self (ולכן כתיבת ^ self בסוף מתודה היא כפילות מיותרת). ואם מתודה לא נמצאה עד ל-ProtoObject, נשלחת למקבל ההודעה doesNotUnderstand:!

1. הקישור הדינמי (8.5) ואלגוריתם איתור המתודות במעלה העץ (8.6)

איך מערכת זמן הריצה מוצאת את המתודה הנכונה, ומה קורה כשהיא מגיעה ל-ProtoObject בידיים ריקות?

משלוח הודעה לעצם אינו שקול לקריאת פונקציה מסורתית. עוצמתה של Smalltalk נשענת על מנגנון הקישור הדינמי (Late Binding / Dynamic Dispatch): תהליך איתור המתודה המדויקת שתבוצע נדחה עד לרגע הביצוע בזמן הריצה, ונגזר בלעדית מזהות המחלקה הדינמית של העצם שקיבל את ההודעה באותו חלקיק שנייה (ללא קשר לאופן הכרזת המשתנה, מה גם ש-Smalltalk היא ממילא שפה נטולת טיפוסים סטטיים).

כאשר נשלחת הודעה לעצם, מערכת זמן הריצה פותחת במסע חיפוש שיטתי במעלה עץ הירושה — אלגוריתם איתור המתודות (Method Lookup):

צעד 1 זיהוי מחלקת המקבל

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

צעד 2 חיפוש ב-methodDict

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

צעד 3 טיפוס במעלה העץ

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

צעד 4 (כישלון) doesNotUnderstand:

אם החיפוש הגיע עד ל-ProtoObject והמתודה לא אותרה, המערכת אורזת את הסלקטור והארגומנטים במופע של Message ושולחת למקבל המקורי את ההודעה doesNotUnderstand:!

🚨 הכוח של doesNotUnderstand:

ברירת המחדל לטיפול ב-doesNotUnderstand: במחלקת Object היא זריקת חריגה (Exception) מסוג MessageNotUnderstood, עצירת התוכנית ופתיחת חלון מנפה השגיאות (Debugger).
אולם, מכיוון שזוהי הודעה לכל דבר, מתכנתים יכולים לדרוס (Override) את doesNotUnderstand: במחלקות מותאמות כדי לתפוס הודעות דינמיות שלא הוגדרו מראש ולממש מנגנוני מעטפת (Proxies), תיעוד קריאות דינמי (Logging), או ניתוב הודעות מרוחק (Forwarding)!

↩️ החזרת ערך ממתודה (^ מול ברירת המחדל self)

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

2. דריסה מלאה (Overriding) מול הרחבה (Extension) וההבחנה בין self ל-super

דוגמאות הבוחן defaultColor ו-initialize, לולאת הקסם האינסופית, ו-5 צבעי כפתור ה-inheritance

כאשר תת-מחלקה יורשת ממחלקת-על, היא יכולה לעדכן את ההתנהגות שירשה באחת משתי דרכים מרכזיות:

1. דריסה מלאה (Overriding) – החלפת המימוש ללא super:
Morph >> defaultColor
    "answer the default color/fill style for the receiver"
    ^ Color blue
EllipseMorph >> defaultColor
    "answer the default color/fill style for the receiver"
    ^ Color yellow
2. הרחבה (Extension) – שימור לוגיקת האב עם super:
BorderedMorph >> initialize
    super initialize.
    borderWidth := 1.
    borderColor := Color black

הקריאה super initialize בתחילת המתודה מבטיחה שמשתני המופע של מחלקות-העל יאותחלו כהלכה לפני אתחול השדות המקומיים של הבן!

⚡ הבנת המנגנון של self מול super (ולמה טעות ביניהם גורמת ללולאה אינסופית!)

שני משתני הפסאודו (Pseudo-variables) self ו-super מצביעים בזמן ריצה על אותו עצם מקבל בדיוק בזיכרון (self == super)! ההבדל היחיד והקריטי ביניהם מתבטא בנקודת ההתחלה שממנה יחל מסע חיפוש המתודה במעלה עץ הירושה:
• קריאה דרך self (חיפוש דינמי מלא): מנתבת את מערכת זמן הריצה להתחיל את חיפוש המתודה במחלקת המופע המקורית (הטיפוס הדינמי) של העצם שקיבל את ההודעה בזמן הריצה.
• קריאה דרך super (עוגן סטטי): מאלצת את המערכת לעקוף את המימוש המקומי ולהתחיל את חיפוש המתודה ישירות במחלקת-העל (Superclass) של המחלקה שבה נכתב הקוד המבצע את הפניה בפועל (נקבע מראש בשעת הקומפילציה).
💥 סכנת לולאת הקסם (Infinite Recursion): אילו במתודה BorderedMorph>>initialize היינו כותבים בטעות self initialize במקום super initialize, מנוע החיפוש היה מתחיל שוב במחלקת המופע BorderedMorph, מוצא שוב את אותה מתודה initialize עצמה, קורא לה שוב ושוב, ונתקע בלולאה אינסופית שתקריס את המערכת!

🎨 חיווי 5 הצבעים של כפתור ה-inheritance ב-System Browser

כאשר בוחרים מתודה ב-System Browser, צבע הכפתור inheritance מספק אבחנה חזותית מיידית לגבי מעמד המתודה בשרשרת הירושה:

🌸 ורוד (Pink) דריסה מלאה (Overriding) של מתודה ממחלקת-העל ללא שימוש ב-super.
🟢 ירוק (Green) הרחבה (Extension) של מתודה ממחלקת-העל תוך שימוש ב-super.
🟡 זהב / צהוב (Gold) המתודה מוגדרת מחדש (נדרסת) בתת-מחלקה עוקבת במורד ההיררכיה.
🟠 כתום (Orange) שילוב: דורסת מתודה ממחלקת-העל (ללא super) ובנוסף נדרסת בתת-מחלקה.
🟣 סגול (Purple) שילוב: מרחיבה מתודה ממחלקת-העל (עם super) ובנוסף נדרסת בתת-מחלקה.
עזר גרפי 8.4 • מסלול איתור מתודות ו-self מול super

אלגוריתם הטיפוס במעלה עץ הירושה עד ProtoObject ונקודת ההתחלה של self לעומת super

Method Lookup & self vs. super
EllipseMorph (מחלקת המופע המקבל) defaultColor ➔ ^ Color yellow BorderedMorph (מחלקת-על ביניים) initialize ➔ super initialize. borderWidth := 1 Morph ➔ Object ➔ ProtoObject לא נמצא ב-ProtoObject? ➔ שלח doesNotUnderstand:! קריאה דרך self: מתחילה תמיד במחלקת המופע הדינמית של העצם המקבל! ⚠️ self initialize בתוך initialize יגרום ללולאה אינסופית! קריאה דרך super: מתחילה במחלקת-העל של המחלקה שבה כתוב הקוד! ✓ super initialize עוקף את הבן ומאתחל בבטחה את האב!
🎨

מיני-סימולציה 8.4: מפענח צבעי כפתור inheritance ב-System Browser

לחץ על כל צבע כדי לראות את משמעותו המדויקת בהיררכיה
🧭
סימולציה אינטראקטיבית גדולה 8.4

מעבדת איתור המתודות (Method Lookup), לולאת הקסם של self מול super ו-doesNotUnderstand:

בחר תרחיש הרצה על מופע של EllipseMorph או BorderedMorph ועקוב אחר מסלול החיפוש במעלה העץ (EllipseMorph → BorderedMorph → Morph → Object → ProtoObject):

⚡

הגשר הנרטיבי: הבנו איך עצמים שולחים הודעות ומאתרים מתודות – אבל איך משתפים מידע בין עצמים רבים ללא העברת פרמטרים מסורבלת?

עד עכשיו ראינו משתני מופע (ששייכים למופע בודד) ומשתני מופע של מחלקה (ששייכים לעצם מחלקה בודד). אבל מה קורה כשעולה הצורך לשתף נתונים בין מספר רב של עצמים ומחלקות במקביל? ביחידה המסכמת הבאה נכיר את שלוש הקטגוריות של משתנים משותפים (Shared Variables) — משתנים גלובליים במילון Smalltalk, משתני מחלקה (Class Variables) ומילוני מאגר משותפים (Pool Dictionaries) — ונחתום את פרק 8 עם כרטיסיות הזיכרון התלת-ממדיות!

פרק 8: מודל התכנות ב-Smalltalk • יחידה 8.7 וסיכום משתנים משותפים ואינטגרציה של הפרק

משתנים משותפים (Global, Class, Pool Dictionaries) ומילון מושגים אינטראקטיבי לפרק 8

שלוש קטגוריות המשתנים המשותפים המתחילים באות גדולה, מילון המערכת Smalltalk (SystemDictionary), ההבחנה המכרעת בין משתנה מחלקה למשתנה מופע של מחלקה, מילוני מאגר (SharedPool), ו-15 כרטיסיות זיכרון תלת-ממדיות לסיכום הפרק.

🌐
🧠

מודל מנטלי ועיקרון מנחה: אות רישית למשתנים משותפים — ואבחנה חדה בין עותק משותף לעותק פרטי!

💡 האנלוגיה המוחשית

משתנה גלובלי הוא כיכר העיר שכולם רואים; משתנה מחלקה הוא קופה משפחתית אחת שמשותפת להורים ולכל הילדים; ומילון מאגר (Pool) הוא מועדון חברים סגור למחלקות שנרשמו אליו גם בלי קשר משפחתי!

⚖️ חוק הברזל הפדגוגי

לכל שלוש הקטגוריות של משתנים משותפים ב-Smalltalk יש חוק תחבירי משותף אחד: שמם מתחיל תמיד באות רישית (גדולה – Capital Letter)!

🎯 תובנת המפתח

משתנה מחלקה (classVariableNames:) מתחיל באות גדולה ויש ממנו עותק יחיד ומשותף לכל עץ הירושה; לעומתו משתנה מופע של מחלקה מתחיל באות קטנה ולכל תת-מחלקה יש עותק פרטי ומבודד!

1. משתנים גלובליים (Global Variables) ומילון המערכת Smalltalk

איך נשמרים המשתנים הגלובליים ב-SystemDictionary, אילו עצמים גלובליים חיים ב-Squeak, ואיך יוצרים ומוחקים אותם?

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

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

1. Transcript מופע של TranscriptStream המשמש כזרם וחלון פלט להדפסת הודעות מערכת בקונסולה.
2. Smalltalk אובייקט מילון המערכת הראשי עצמו (מופע של SystemDictionary המצביע על עצמו!).
3. Sensor מופע של EventSensor לניהול וקליטת אירועי קלט חיצוניים כגון עכבר ומקלדת.
4. World מופע של PasteUpMorph המייצג את סביבת/שטח העבודה החזותית הראשית (חלון הרקע – "העולם").
5. ActiveHand מופע של HandMorph המייצג את סמן העכבר הפעיל הנוכחי בעולם.
6. Undeclared מילון SystemDictionary ייעודי לאחסון שמות משתנים שנעשה בהם שימוש בקוד לפני שהוגדרו במפורש.
7. SystemOrganization מופע של SystemOrganizer המנהל מידע ארגוני וקטלוג הקטגוריות של המחלקות.
8. שמות המחלקות כל מחלקה במערכת (כגון Object, Point, String) היא משתנה גלובלי במילון Smalltalk!
"ניהול משתנים גלובליים ישירות דרך מילון המערכת Smalltalk (או בכתיבת מילה באות גדולה ב-Workspace):"
Smalltalk at: #MyVariable put: 10.   "או Smalltalk at: #MyGlobal put: 100"
Smalltalk removeKey: #MyVariable.  "או Smalltalk removeKey: #MyGlobal - הסרת המשתנה הגלובלי"

2. משתני מחלקה (Class Variables) ומילוני מאגר (Pool Dictionaries)

ההבחנה הקריטית מול משתני מופע של מחלקה, ושיתוף משתנים בין מחלקות ללא קשר ירושה (SharedPool)

🏛️ 2. משתני מחלקה (Class Variables – classVariableNames:)

שקולים בתפקודם למשתנים סטטיים (static) משפות כמו Java או ++C.
• תחום הכרה ומחזור חיים: משותפים לכל המופעים שנוצרו ע"י המחלקה, וכן לכל תת-המחלקות שיירשו ממנה ולכל המופעים שלהן! נוצרים ומתקיימים לאורך כל חיי המחלקה.
• הגדרה ואתחול: מוכרזים בשדה classVariableNames: בעת בניית המחלקה (לדוגמה, משתני טבלאות וסיווג במחלקה Character). נהוג לאתחלם במתודת ה-initialize של המטה-מחלקה (עם טעינת הקוד).
• מניעת צימוד הדוק: גישה ישירה למשתנה מחלקה מתוך מתודות מופע עלולה ליצור צימוד הדוק למבנה; הפתרון הנפוץ הוא הקמת מתודות גישה (Accessors) ברמת המחלקה שמופעים פונים דרכן.

🏊 3. משתני מאגר / מילוני מאגר (Pool Dictionaries – poolDictionaries:)

נועדו לפתור את הצורך לשתף קבוצת משתנים או קבועים בין קבוצת מחלקות מגובשת, גם כאשר אין ביניהן שום קשר של ירושה, מבלי לזהם את המרחב הגלובלי!
• איך זה עובד? יוצרים תת-מחלקה היורשת מ-SharedPool ומאחסנים בה את משתני המאגר. כל מחלקה שזקוקה לגישה מכריזה על שמו בשדה poolDictionaries: בהגדרתה.
• דוגמה: המאגר TextConstants (פונטים ומרווחי שורות) המשותף למחלקות כגון Text ו-TextStyle.
• מעמד הנדסי: שימוש במילוני מאגר נחשב לפרקטיקה לא מומלצת (Bad Practice) ב-OOP מודרני ומומלץ להימנע ממנו.

⚖️ טבלת ההבחנה המכרעת: משתנה מחלקה (Class Variable) מול משתנה מופע של מחלקה (Class Instance Variable)

משתנה מחלקה (Class Variable)

• היכן מוגדר? בשדה classVariableNames: בהגדרת המחלקה.
• אות פותחת: מתחיל תמיד באות גדולה (Capital Letter).
• התנהגות בירושה: קיים ממנו עותק יחיד ומשותף לכל עץ הירושה שתחת המחלקה המגדירה! שינוי ערכו ע"י תת-מחלקה משפיע על כל שאר המחלקות והמופעים.

משתנה מופע של מחלקה (Class Instance Variable)

• היכן מוגדר? בשדה instanceVariableNames: של המטה-מחלקה (למשל Dog class).
• אות פותחת: מתחיל תמיד באות קטנה (Lowercase).
• התנהגות בירושה: לכל מחלקה בהיררכיה יש עותק פרטי, נפרד ומבודד משלה (כמו במונה count של Dog ו-Hyena או ב-uniqueInstance של Singleton)!

עזר גרפי 8.5 • מפת תחומי ההכרה והשיתוף של משתנים ב-Smalltalk

השוואה ויזואלית: משתני מופע, משתני מופע של מחלקה, משתני מחלקה, מילוני מאגר ומשתנים גלובליים

Variable Scopes Map
1. משתנה מופע (אות קטנה) עותק פרטי לכל אובייקט (מופע) בנפרד. נגיש רק ל-self ולתתי-המחלקות! 2. מופע של מחלקה (אות קטנה במטה-מחלקה) עותק פרטי ומבודד לכל מחלקה בעץ הירושה! דוגמה: count ב-Dog ו-uniqueInstance 3. משתנה מחלקה (אות גדולה: Class Var) עותק יחיד ומשותף למחלקה, לתת-המחלקות ולכל המופעים שלהן! דוגמה: Character 4. מילון מאגר (Pool) (אות גדולה: SharedPool) שיתוף בין קבוצת מחלקות ללא קשר ירושה (poolDictionaries:). TextConstants (Bad Practice) 5. משתנה גלובלי (אות גדולה במילון Smalltalk) נגיש מכל מקום במערכת בתוך SystemDictionary. Transcript, World, Sensor, ActiveHand, Undeclared
🌐

מיני-סימולציה 8.5: חוקר מילון המערכת Smalltalk (SystemDictionary) והמשתנים המשותפים

לחץ על כל קטגוריה לבדיקת אופן הגדרתה ופעולתה
🃏
סימולציה אינטראקטיבית גדולה 8.5

כרטיסיות זיכרון תלת-ממדיות (3D Flashcards) לכל 15 מושגי פרק 8

לחץ על כל כרטיסייה כדי להפוך אותה ולבחון את עצמך על כל אחד מ-15 מושגי היסוד של פרק 8:

נחשפו: 0 / 15
🚀

סיימנו במלואו את פרק 8! מה מחכה לנו בפרק 9?

בפרק 8 פיצחנו את כל המנגנונים הפנימיים של מודל התכנות בשפת Smalltalk: חמשת עקרונות המודל, כללי משלוח ההודעות, כמוסות מבוססת-עצם, מטה-מחלקות, משתני מופע של מחלקה ותבנית ה-Singleton, מחלקות מופשטות (subclassResponsibility), תבנית Template Method, מנגנון ה-Traits, איתור מתודות (Method Lookup), self מול super, doesNotUnderstand: ושלוש קטגוריות המשתנים המשותפים!

🎯 התחנה הבאה שלנו – פרק 9: סביבת העבודה Squeak: בפרק 9 נצלול אל הכלים המתקדמים של הסביבה: Package Browser, כלי השיתוף Monticello, עבודה מעשית ודיבוג עם ה-Debugger, כלי ה-Message Names, ניהול שינויים באמצעות Change Sorter ו-Change Sets, ושחזור קוד שאבד!
🕹️

מעבדת חקר אינטראקטיבית

❓

5 שאלות פתוחות מדורגות + 5 שאלות אמריקאיות עם הסברים מלאים לכל יחידה

🚀 התחנה הבאה שלנו: פרק 9 – סביבת העבודה Squeak

סיימנו במלואו את פרק 8: מודל התכנות בשפת Smalltalk! בפרק 9 נצלול יחד אל הכלים המתקדמים של סביבת העבודה Squeak: