בפסקל מונחה עצמים יש לנו אפשרות מאוד נחמדה לקבל מחלקה בזמן ריצה בתור פרמטר, ובעצם לקבל את היכולת לעבוד איתה גם אם המתודות והתכונות שלה אינן מוגדרות אצל האב.
אפשרות זו נקראת class reference או metaclass. האפשרות הזו מדברת שבזמן תכנון איננו יודעים מה המחלקה המדוייקת שאותה נקבל בתור פרמטר, והיא נקבעת רק בזמן ריצה !
עד כמה שיודע לי, למעט שפת פסקל ו objective c כל השפות האחרות שתומכות באפשרות הינן שפות דינמיות כדוגמת פיתון, רובי וכו' (את הרשימה המלאה אפשר למצוא בקישור שלי ל wikipedia).
אז כיצד עובדים עם Metaclass ?
ובכן דבר ראשון נגדיר מחלקה רגילה:
type
TMyNormalClass = class(TSomeParent)
...
procedure PushMessage(const Msg : String); virtual; abstrct;
...
end;
השלב הבא הוא להגדיר את ההגדרה הבאה:
TMyNormalClasses = class of TMyNormalClass;
מה שהגדרנו כאן זה התייחסות שכל דבר שהוא או TMyNormalClass או יורש ממנו יתקבל על ידי הטיפוס TMyNormalClasses.
כיצד נשתמש בזה אתם בטח שאולים את עצמכם, ובכן בצורה הבאה:
procedure PushMessage(const Msg : String; AInstance : TMyNormalClass; ANormalClass : TMyNormalClasses);
begin
if ANormalClass = TMyNormalClass then
raise Exception.Create('The given class is an abstract class.');
(AInstance as ANormalClass).PushMessage(Format('[%s] %s', [DateTimeToStr(now), Msg]);
end;
כמו שניתן לראות, בדוגמא המאוד מופשטת שלנו, אנחנו שולחים הודעה כלשהי לאיזשהי מחסנית (כנראה) או משהו בסגנון שמחלקה שתירש מ TMyNormalClass צריכה ליישם בעצמה.
הסיבה ששמנו = ולא השתמשנו ב is היא בגלל שהמצב שלנו כאן הוא ש ANormalClass מכיל מחלקה ולא Instance, ולכן אנחנו לא שואלים אם ה instance הוא מחלקה, אלא אנחנו שואלים האם מחלקה מסויימת היא המחלקה שאנחנו רוצים.
במידה ונקבל את המחלקה המקורית, שבה הגדרנו מתודת מבנה ווירטואלית, אז הפרוצדורה שלנו תעלה חריגה לאוויר ותסיים את חייה ללא שימוש במתודה עצמה.
השורה של (AInstance as ANormalClass) היא השורה שעושה את הטריק. היא אומרת ל AInstance להתנהג בתור ANormalClass ולא בתור ה Instance שאנחנו מעבירים לה.
כיצד נשתמש בזה בפועל ?
type
TMyNormalImpelementation = class(TMyNormalClass)
...
procedure
TMyNormalImpelementation.
PushMessage(const Msg : String); override;
...
end;
...
var
MyInstance : TMyNormalClass;
...
begin
...
MyInstance := TMyNormalImpelementation.Create .... ;
...
PushMessage('Hello world', MyInstance, TMyNormalImpelementation);
...
end.
ובעצם עכשיו כל מימוש של הרשימה שלנו (כולל בנים שירשו מ TMyNormalImpelementation יוכלו להיכנס בתור פרמטר ללא בעיה והמהדר שלנו לא ילין על כך בכלל.
חשוב להבין שגם אחרי שאתחלנו את MyInstance בתור TMyNormalImpelementation, אנחנו יכולים להעביר כל class שיורש מ TMyNormalClass.
מזכיר מאוד את reflection שיש ב-Java ו- C#
reflection זה הנושא הבא שאני הולך לדבר עליו (ולהציג איך לעשות), וזה שונה מהרעיון כאן, אם כי הוא שייך לאותה המשפחה.
אולי אני מפספס משהו (אין הזחה או צביעת תחביר ומקשה קצת על הקריאה), אך מה שמתואר כאן אינו metaclass, אלא עץ ירושה רגיל.
להבדיל, אפשר לחשוב על metaclass בתור מחלקה אשר עוזרת ביצירת מחלקות אחרות.
מאיר, אתה לא מפספס משהו, אלא הדגמתי עוד שימוש לזה. השימוש שאתה מדבר עליו הוא כמו בדוגמא בכתובת הבאה:
http://freepascal.org/docs-html/ref/refse30.html
תחפש class reference בתוך הדף הזה. שם הם מדגימים יצירה של מחלקה באמצעות המחלקה שהעברת בתור פרמטר.
זוהי אחת מתוך כמה דוגמאות של דברים שאפשר ליצור בעזרת השיטה הזו.
הדוגמה לא ברורה. בשביל מה בכלל אתה צריך לכתוב את הפונקציה הזו? זה לא תפקידו של הקומפיילר?
כשאתה כותב תוכנה רגילה, ואתה שולט בכל המרכיבים שלה (קרי לא תוכנה גדולה), אין לך לרוב שימוש לזה, אלא אם אתה רוצה לאחד קוד ולעבוד בשיטת DRY.
הדוגמא שהבאתי כאן, לא מציגה משהו שהוא באמת נחוץ אלא נועדה יותר להדגים את (אחד) השימושים של זה.
מקום שאתה יכול למצוא את זה בשימוש זה נגיד בספריית sockets (למשל lnet עובדת עם זה), כאשר אתה מקבל socket שיכול להיות כל סוג שיורש מ TLSocket, ואז אם יצרתי socket שבעצם מדבר ב HTTP, אני לא צריך לכתוב משהו מיוחד בשביל להשתמש באותן מתודות בשביל לעשות פעולות מסויימות רק בגלל שהמחלקה שונה.
אני מקווה שזה יותר ברור.
את האמת — זה לא ממש ברור — ואני עוד זוכר משהו מ־pascal.
אני באמת איבדתי אותך: לפי ההסבר מדובר בהחלפת virtual method במקום. (אם אני מבין נכון) קרי, יש לך vtable של אובייקט מסוים, אתה מחליף בו X ב־Y. האם זה נכון או לא? או שמא מדובר ב־dynamic casting?
ארתיום, אני בספק שלמדת את הנושא הספציפי הזה.
בכל מקרה, אני אכתוב חלק שני לפוסט הזה עם דוגמא יותר רלוונטית, ואולי זה יהיה יותר ברור.
מה שמתואר בקישור של FPC אכן מדגים metaclass. לעומת זאת הקוד אשר מופיע כאן אמנם משתמש ב-class reference, אך רק עבור בדיקת ירושה ואינו מדגים metaclass.
מאיר, תקן אותי אם אני טועה, אבל meta class לא מדבר רק על יצירה, אלא גם על שימוש. אני לא הדגמתי יצירה, היות וזה הדבר הכי נפוץ (אבל לא היחיד) שיש עם זה, וכל אחד כמעט ימצא מידע רק על יצירה.
הבעיה בדוגמא שלי היא, שאין באמת צורך למה שעשיתי, כי פישטתי אותו יותר מידי, ובגלל זה אני מחפש עכשיו צורה אחרת להדגים מה זה אומר.
שוב, אין כאן שימוש ב-metaclass, יש שימוש ב-class reference.
כן ולא. אני לא יודע איך בפיתון, אתה משתמש (לא הצלחתי להבין את הדוגמא בוויקיפדיה) בmeta class
אבל בפסקל סוג אחד ספציפי של class reference נקרא meta class.
שוב אני יכול ליצור איתו עוד רכבים, אבל לא רק. יכול להיות שבפיתון הכוונה היא רק ליצור עוד אוביקטים, אבל לא בפסקל.
בפסקל תכונת ה-class reference מאפשרת metaclass. בפייתון, עקב האופי הדינאמי של השפה, משהו כמו class reference מיותר.
בדוגמא בוויקיפדיה ה-AttributeInitType (בניצול ה-__call__) מחזיר לך מחלקה שהפרמטרים בעלי השם (kwargs) אשר מועברים בעת הקריאה אליה הופכים ל-attributes שלה. משמתשים בו בתור metaclass בחילול האובייקט Car אשר זוכה בתכונה הזו.
פינגבק: meta class 2 « לראות שונה
פינגבק: מערך כתכונה | לראות שונה