מימוש סינגלטון ב Ruby
הבעיה עם רובי היא שבדיוק כשאתה חושב שאתה מבין מה קורה היא מצליחה להפתיע ולעקוף את ההגנות של עצמה. כך הסיפור עם תבנית ה Singleton.
מימוש סינגלטון ברובי הוא דבר די פשוט - מאחר ויש מקום לכתוב קוד איתחול שייקרא בעת טעינת הקלאס, נוכל להגדיר משתנה סטטי ולאתחל אותו בזמן הטעינה:
class Critter
@@instance = Critter.new
def self.instance
@@instance
end
def val
5
end
end
ואכן יש לנו מחלקה Critter עם מתודה בשם instance שמחזירה תמיד את אותו אוביקט:
c = Critter.instance
d = Critter.instance
puts c == d # true
רובי אפילו הלכה צעד אחד קדימה ונתנה קיצור דרך לתבנית הזאת בדמות מודול בשם singleton שבנוי כבר בשפה. הקוד הבא עושה בדיוק את אותו הדבר:
require 'singleton'
class Critter
include Singleton
def val
5
end
end
c = Critter.instance
d = Critter.instance
puts c.val
puts d == c
הבעיות מתחילות כשננסה להסתיר את הפונקציה new. כאן לרובי יש סוג של פיתרון באמצעות הפונקציה private_class_method
. כך נראה Critter שמסתיר את פונקציית new שלו:
class Critter
include Singleton
def val
5
end
private_class_method :new
end
ובאמת מי שינסה לקרוא עכשיו ל Critter.new יקבל את השגיאה:
NoMethodError: private method `new' called for Critter:Class
הבעיה שרובי מספקת עוד כמה דרכים להפעיל פונקציות, לדוגמא באמצעות הפקודה send. הקוד הבא עובד ומדפיס false:
require 'singleton'
class Critter
include Singleton
def val
5
end
private_class_method :new
end
c = Critter.instance
d = Critter.send(:new)
puts d.val # print 5
puts d == c # false
צריך לזכור שהמעבר לשפה דינמית כרוך בשינויי תפיסה מסוימים, המרכזי שבהם הוא אובדן תחושת השליטה. אנחנו לא אומרים למתכנתים אחרים שהם לא יכולים לקרוא לפונקציה מסוימת, כי תמיד תימצא דרך. במקום זה נגיד שאנחנו ממליצים לא להפעיל את זה, או שאם תיצור עוד מופע מ Singleton דברים כנראה יישברו ואתה תצטרך לתקן את זה. זאת המשמעות האמיתית של private ב Ruby ולכן גם של private_class_method
.