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

نمط Observer

جزء من قسم البرمجة كائنية التوجه في رحلة Ruby على Coddy. الدرس 44 من 57.

يتيح نمط المراقب للكائنات الاشتراك في تغييرات كائن آخر، وهو الموضوع. يحتفظ الموضوع بقائمة من المراقبين ويُخطِر كلًّا منهم عند حدوث شيء ما، من دون أن يعرف ما الذي سيفعلونه به:

class Thermometer
  def initialize
    @observers = []
  end

  def subscribe(observer)
    @observers << observer
  end

  def temperature=(value)
    @temperature = value
    @observers.each { |o| o.update(value) }
  end
end

class Display
  def update(value)
    puts "display: #{value} C"
  end
end

class Alarm
  def update(value)
    puts "alarm: too hot!" if value > 30
  end
end

t = Thermometer.new
t.subscribe(Display.new)
t.subscribe(Alarm.new)
t.temperature = 25
t.temperature = 35

الناتج:

display: 25 C
display: 35 C
alarm: too hot!

لا يحتاج المراقبون إلا إلى طريقة update: الكتابة بأسلوب البطّة. يمكن أن يكون كلٌّ منها فئة مختلفة تمامًا، ويمكن إضافة مراقبين جدد دون تغيير الموضوع:

class Logger2
  def update(value)
    @history = (@history || []) << value
  end
end

thermometer.subscribe(Logger2.new)   # الموضوع لم يتغير

يمكن أن تكون المراقبات كتلًا أيضًا. يخزّنها الكائن كـ procs ويستدعيها باستخدام call. يزيل unsubscribe مراقبًا مرة أخرى:

class Button
  def initialize
    @handlers = []
  end

  def on_click(&handler)
    @handlers << handler
    handler
  end

  def unsubscribe(handler)
    @handlers.delete(handler)
  end

  def click
    @handlers.map { |h| h.call("clicked") }
  end
end

b = Button.new
b.on_click { |e| "A got #{e}" }
second = b.on_click { |e| "B got #{e}" }
p b.click
b.unsubscribe(second)
p b.click

الناتج:

["A got clicked", "B got clicked"]
["A got clicked"]

يحدد الموضوع ما سيتم إرساله. يتيح تمرير الموضوع نفسه للمراقب قراءة ما يحتاج إليه؛ بينما يتيح تمرير اسم حدث لمراقب واحد الاستجابة لعدة أنواع من التغييرات:

@observers.each { |o| o.update(self) }          # المراقب يقرأ subject.price
@observers.each { |o| o.update(:sold_out, self) }  # المراقب يتحقق من الحدث
challenge icon

التحدي

سهل

أكمل الصنف Stock ومراقبَين. يوفّر Stock.new(symbol) الدالتين subscribe(observer) وupdate_price(value)؛ إذ يخزّن السعر ويعيد الردود غير-nil الناتجة عن update(stock) لدى كل مراقب، وفق ترتيب الاشتراك. تعيد PriceDisplay#update القيمة AAPL now 12.50. وتعيد PriceAlert.new(limit) القيمة ALERT AAPL above 100.00 عندما يرتفع السعر متجاوزًا الحد بعد أن كان عنده أو دونه، وتعيد nil بخلاف ذلك.

يحتوي سطر الإدخال الأول على الرمز وحد التنبيه. يشترك الكود الموفَّر في عرض وتنبيه، ثم يطبع الردود لكل سطر set price.

ضع كودك في stock.rb وprice_display.rb وprice_alert.rb. يحتوي main.rb على كود الإدخال/الإخراج الموفَّر ولا يمكن تعديله.

جرّب بنفسك

require_relative 'stock'
require_relative 'price_display'
require_relative 'price_alert'

# كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
input = []
while (line = gets)
  input << line.chomp
end
symbol, limit = input[0].split
stock = Stock.new(symbol)
stock.subscribe(PriceDisplay.new)
stock.subscribe(PriceAlert.new(limit.to_f))
input.drop(1).each do |line|
  stock.update_price(line.split[1].to_f).each { |reply| puts reply }
end
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Ruby عبر الإنترنت