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

نمط Observer

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

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

make_subject <- function() {
  observers <- list()
  list(
    subscribe = function(f) observers[[length(observers) + 1]] <<- f,
    notify = function(event) for (f in observers) f(event)
  )
}
button <- make_subject()
button$subscribe(function(e) cat("logger saw", e, "\n"))
button$subscribe(function(e) cat("counter saw", e, "\n"))
button$notify("click")

الناتج:

logger saw click 
counter saw click 

يمكن أن يكون المراقب إغلاقًا له حالته الخاصة. هذا المراقب يحسب الأحداث التي رآها، بينما لا يعرف الكائن موضوع المراقبة شيئًا عن عملية العد:

make_subject <- function() {
  observers <- list()
  list(subscribe = function(f) observers[[length(observers) + 1]] <<- f,
       notify = function(event) for (f in observers) f(event))
}
make_counter <- function() {
  n <- 0
  list(observe = function(event) n <<- n + 1, count = function() n)
}
door <- make_subject()
c1 <- make_counter()
door$subscribe(c1$observe)
for (e in c("open", "close", "open")) door$notify(e)
print(c1$count())

الناتج:

[1] 3

يتيح تخزين المُراقبين تحت أسماء لهم إلغاء الاشتراك. يؤدي observers[[name]] <<- NULL إلى إزالة أحدهم، بينما يستمر الآخرون في تلقي الأحداث:

make_subject <- function() {
  observers <- list()
  list(
    subscribe = function(name, f) observers[[name]] <<- f,
    unsubscribe = function(name) observers[[name]] <<- NULL,
    notify = function(event) for (f in observers) f(event)
  )
}
s <- make_subject()
s$subscribe("ada", function(e) cat("ada:", e, "\n"))
s$subscribe("bo", function(e) cat("bo:", e, "\n"))
s$notify("first")
s$unsubscribe("ada")
s$notify("second")

الناتج:

ada: first 
bo: first 
bo: second 

يحدد الموضوع البيانات التي يحملها الحدث. يمرر مؤشّر الأسعار السعر القديم والسعر الجديد، ويُخطِر فقط عندما يتغير السعر فعليًا:

make_ticker <- function(price) {
  observers <- list()
  list(
    subscribe = function(f) observers[[length(observers) + 1]] <<- f,
    set_price = function(new) {
      if (new == price) return(invisible(NULL))
      old <- price
      price <<- new
      for (f in observers) f(old, new)
    }
  )
}
t <- make_ticker(10)
t$subscribe(function(old, new) cat("price", old, "->", new, "\n"))
for (p in c(12, 12, 9)) t$set_price(p)

الناتج:

price 10 -> 12 
price 12 -> 9 
challenge icon

التحدي

سهل

أكمل دالة الإنشاء make_newsletter() في Newsletter.R. فهي تحتفظ باستدعاءات القرّاء في قائمة خاصة مُسمّاة، وتُرجع ثلاث دوال:

  • تخزّن subscribe(name, f) الاستدعاء تحت الاسم (ويؤدي الاشتراك مرة أخرى إلى استبداله)؛
  • تزيل unsubscribe(name) الاستدعاء؛
  • تستدعي publish(title) كل استدعاء مع العنوان، بترتيب الاشتراك، وتُرجع عدد الذين تم إخطارهم.

يقرأ الكود المزوّد الأوامر sub ada وunsub ada وpost Big news، ويمنح كل قارئ استدعاءً يطبع ada got: Big news. ولكل منشور، يطبع العنوان، وأسطر الاستدعاءات، وعدد الذين تم إخطارهم.

يجب أن تضع كودك في Newsletter.R. يحتوي main.R على كود الإدخال/الإخراج المزوّد ولا يمكن تعديله.

جرّب بنفسك

source("Newsletter.R")

# كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
input <- suppressWarnings(readLines(file("stdin")))
news <- make_newsletter()
reader <- function(name) {
  force(name)
  function(title) cat("  ", name, " got: ", title, "\n", sep = "")
}
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  if (p[1] == "sub") news$subscribe(p[2], reader(p[2]))
  if (p[1] == "unsub") news$unsubscribe(p[2])
  if (p[1] == "post") {
    title <- paste(p[-1], collapse = " ")
    cat("post: ", title, "\n", sep = "")
    n <- news$publish(title)
    cat("  notified ", n, "\n", sep = "")
  }
}
quiz iconاختبر نفسك

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

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

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