Menu
Coddy logo textTech

תבנית Singleton

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Ruby של Coddy. שיעור 42 מתוך 57.

תבנית עיצוב היא פתרון מוכח לבעיית עיצוב נפוצה. תבנית singleton מבטיחה שלמחלקה יש בדיוק מופע אחד, שניתן לגשת אליו מכל מקום. ספריית התקן של Ruby כוללת אותה: require 'singleton' ו-include Singleton מוסיפים למחלקה מתודה instance:

require 'singleton'

class Settings
  include Singleton
  attr_accessor :theme

  def initialize
    @theme = "light"
  end
end

Settings.instance.theme = "dark"
puts Settings.instance.theme
puts Settings.instance.equal?(Settings.instance)

פלט:

dark
true

include Singleton גם הופך את new לפרטית, כך שלא ניתן ליצור מופע שני. כל חלק בתוכנית שמבקש את instance מקבל את אותו אובייקט:

require 'singleton'

class Counter
  include Singleton
  attr_reader :hits

  def initialize
    @hits = 0
  end

  def hit
    @hits += 1
  end
end

def page_a
  Counter.instance.hit
end

def page_b
  2.times { Counter.instance.hit }
end

page_a
page_b
puts Counter.instance.hits
begin
  Counter.new
rescue NoMethodError => e
  puts e.message
end

פלט:

3
private method `new' called for class Counter

אותו רעיון, כשהוא נכתב ידנית, הוא מתודת מחלקה שיוצרת את המופע בשימוש הראשון ומתודת new פרטית. כך אפשר לראות מה המודול עושה עבורך:

class Config
  def self.instance
    @instance ||= new
  end
  private_class_method :new

  attr_accessor :level

  def initialize
    @level = 1
  end
end

Config.instance.level = 3
puts Config.instance.level

פלט:

3

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

# קל יותר לבדוק: ה-logger מועבר כפרמטר
def process(order, log)
  log.write("processing #{order.id}")
end
challenge icon

אתגר

קל

השלימו את הסינגלטון AppLogger באמצעות include Singleton. הוא מחזיק מערך של רשומות ויש לו את log(message, level), ששומר [INFO] message או [ERROR] message (הרמה באותיות גדולות), את entries ואת error_count. פונקציות העזר handle_info(message) ו־handle_error(message) שב־functions.rb רושמות שתיהן דרך AppLogger.instance, ולכן הן מגיעות לאותו אובייקט.

הקוד שסופק קורא שורות מסוג info,message או error,message, שולח אותן דרך פונקציות העזר, מדפיס כל רשומה ואת errors: 1, ובודק אם הקריאה ל־AppLogger.new נדחית.

הקוד שלכם ייכתב בקבצים app_logger.rb ו־functions.rb. הקובץ main.rb מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.

נסו בעצמכם

require_relative 'app_logger'
require_relative 'functions'

# קוד קלט/פלט שסופק: יש להשאיר אותו כפי שהוא
input = []
while (line = gets)
  input << line.chomp
end
input.each do |line|
  level, message = line.split(",", 2)
  level == "error" ? handle_error(message) : handle_info(message)
end
AppLogger.instance.entries.each { |e| puts e }
puts "errors: #{AppLogger.instance.error_count}"
begin
  AppLogger.new
  puts "new is allowed"
rescue NoMethodError
  puts "new is private"
end
quiz iconבחנו את עצמכם

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

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

תרגלו בעצמכם: קומפיילר Ruby אונליין