نمط 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التحدي
سهلأكمل 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
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1الفئات والكائنات
العمل مع الملفاتتعريف الفئاتمتغيرات النسخةالأساليب و selfto_s و inspectمراجعة - بطاقة المكتبة7تعدد الأشكال والكتابة بأسلوب البطة
تعدد الأشكالالكتابة بأسلوب البطةالأساليب المجردةالحالات والفئاتمراجعة - طرق الدفع3أساليب ومتغيرات الأصناف
أساليب الأصنافمتغيرات كائنات الأصنافالثوابتأساليب الإنشاءمراجعة - رموز القسائمتدرّب بنفسك: مترجم Ruby عبر الإنترنت