תבנית 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 אתגר
קלהשלם את מפעל הפונקציות 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 = "")
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות S3
עבודה עם קבציםרשימות עם מחלקהפונקציות בנאימתודות print ו-formatמאמתים ופונקציות עזרחזרה – טמפרטורותתרגלו בעצמכם: קומפיילר R אונליין