Menu
Coddy logo textTech

תבנית Observer

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

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

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 

צופה יכול להיות closure עם מצב משלו. הצופה הזה סופר את האירועים שראה, בעוד שהנושא אינו יודע דבר על ספירה:

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. הוא שומר את פונקציות ה-callback של הקוראים שלו ברשימה פרטית עם שמות, ומחזיר שלוש פונקציות:

  • subscribe(name, f) שומרת את פונקציית ה-callback תחת השם (הרשמה חוזרת מחליפה אותה);
  • unsubscribe(name) מסירה אותה;
  • publish(title) קוראת לכל פונקציות ה-callback עם הכותרת, לפי סדר ההרשמה, ומחזירה כמה קוראים קיבלו הודעה.

הקוד שסופק קורא פקודות sub ada, unsub ada ו-post Big news, ויוצר לכל קורא פונקציית callback שמדפיסה ada got: Big news. עבור כל פרסום הוא מדפיס את הכותרת, את השורות של פונקציות ה-callback ואת מספר הקוראים שקיבלו הודעה.

כתבו את הקוד שלכם בקובץ 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 אונליין