מימוש סינגלטון ב Ruby

12/12/2018

הבעיה עם רובי היא שבדיוק כשאתה חושב שאתה מבין מה קורה היא מצליחה להפתיע ולעקוף את ההגנות של עצמה. כך הסיפור עם תבנית ה 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.