Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط 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

التحدي

سهل

أكمل singleton ‏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 عبر الإنترنت