יצירת מחלקת Singleton בפסקל מונחה עצמים

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

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

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

כאשר אנחנו קרואים ליוצר/בונה (constructor) ששמו בד"כ הוא Create, ישנה פעולה אשר מקדימה את ההרצה שלו. הפעולה שמתבצעת לפני כן מאתחלת בעצם את הזכרון ושמה הוא NewInstance .NewInstace היא פונקציה סטטית אשר מחזירה את TObject.

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

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

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

type
TSingleton = class
...
public
....
class function NewInstance: TObject; override;
procedure FreeInstance; override;
....
end;


var
Singleton : TSingleton = nil;
Counter : Cardinal = 0;


procedure TSingleton.FreeInstance;
begin
Dec(Counter);
if Counter = 0 then
begin
Singleton := nil;
// Destroy private variables here
inherited FreeInstance;
end;
end;


class function TSingleton.NewInstance: TObject;
begin
if ( not Assigned( Singleton ) ) then
begin
Singleton := inherited NewInstance;
end


Result := Instance;
inc(Counter);
end;

אז מה בעצם עושה הקוד ? ובכן הגדרנו מחלקה (זוכרים שהיא יורשת מ TObject ?). הגדרנו את הפונקציה הסטטית NewInstance ואת השיגרה FreeInstance. שימו לב שאין כאן יוצר והורס בכלל ! למעשה רק אם יש צורך ביוצר ו/או הורס אנחנו מגדירים אותם.

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

המשתנה הגלובלי הבא הוא Counter (הוא גם סטטי כמובן), אשר סופר כמה נסיונות יצירה והריסה היו. הרי אם יהיו 2 נסיונות יצירה, אז השיחרור הראשון יהרוס את המשתנה, אבל השיחרור השני יגרום לחריגה (Exception).

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

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

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

השימוש במחלקה היא מאוד פשוטה:

program TestSingleton;
{$MODE OBJFPC}
uses SysUtils, OurSingleToneUnit;

var Single1, Single2 : TSingleton;

begin
Single1 := TSingleton.Create;
Single1.Variable := 1;
writeln('Single1: ', Single1.Variable);

Single2 := TSingleton.Create;
Single2.Variable := 2;

writeln('Single2: ', Single2.Variable);
writeln('Single1: ', Single1.Variable);

Single1.Free;
FreeAndNil(Single2);
end.

כאן אנחנו יוצרים תוכנית אשר תשתמש בSingleton שלנו. אנחנו מחייבים את המהדר לדבר בפסקל מונחה עצמים ע"י שימוש בפקודת המהדר {$MODE}. לאחר מכן, אנחנו יוצרים 2 משתנים שהם בעצם ה"אובייקטים" (אובייקט בפסקל היא מילה שמורה לעוד צורה לעבוד בתכנות מונחה עצמים אכנס לזה בפוסט עתידי) שלנו. אנחנו יוצרים את המשתנה הראשון (שימו לב שבד"כ נהוג לתת ליוצר את המילה Create ובברירת מחדל ב TObject זה גם השם שלו, אבל אין באמת הגבלה והיוצר יכול להיות כל שם שתרצו (פרט לשם המחלקה או שם שמוגדר בתוך המחלקה).

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

אותה פעולה בדיוק עם המשתנה השני, רק השדה/תכונה מקבל ערך של "2".

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

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

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s