הגנה על מצב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-R של Coddy. שיעור 20 מתוך 57.
Setter הוא המקום היחיד שבו ערך יכול להשתנות, ולכן זהו המקום לאכיפת הכללים. הוא בודק תחילה וקורא ל־stop() לפני שמשהו משתנה, והאובייקט נשאר תקין:
make_account <- function(balance = 0) {
withdraw <- function(x) {
if (x <= 0) stop("amount must be positive", call. = FALSE)
if (x > balance) stop("insufficient funds", call. = FALSE)
balance <<- balance - x
}
list(withdraw = withdraw, balance = function() balance)
}
acc <- make_account(50)
msg <- tryCatch(acc$withdraw(80), error = function(e) conditionMessage(e))
cat(msg, "/", acc$balance())פלט:
insufficient funds / 50lockBinding("rate", e) הופכת משתנה אחד בסביבה לקריאה בלבד. הקצאה אליו לאחר מכן היא שגיאה, בעוד ששאר המשתנים נשארים ניתנים לכתיבה:
loan <- new.env()
loan$rate <- 0.05
loan$owed <- 1000
lockBinding("rate", loan)
loan$owed <- 900
msg <- tryCatch({ loan$rate <- 0; "changed" }, error = function(e) conditionMessage(e))
cat(msg, "\n")
cat(loan$rate, loan$owed, "\n")פלט:
cannot change value of locked binding for 'rate'
0.05 900 lockEnvironment(e) מונעת הוספה של משתנים חדשים. עם bindings = TRUE היא גם נועלת כל משתנה קיים, וכך הופכת את האובייקט כולו לקריאה בלבד:
cfg <- new.env()
cfg$theme <- "dark"
lockEnvironment(cfg, bindings = TRUE)
for (step in c("change", "add")) {
msg <- tryCatch({
if (step == "change") cfg$theme <- "light" else cfg$size <- 12
"done"
}, error = function(e) conditionMessage(e))
cat(step, ":", msg, "\n")
}פלט:
change : cannot change value of locked binding for 'theme'
add : cannot add bindings to a locked environment local() מריצה קוד בסביבה חדשה ומחזירה את הערך האחרון שלו. פונקציות עזר שמוגדרות בתוכה נשארות מוסתרות, וכך קובץ יכול להשאיר פונקציה פרטית, אף על פי ש־source() מכניסה את כל השאר לסביבה הגלובלית:
circle_area <- local({
square <- function(x) x * x
function(r) pi * square(r)
})
print(round(circle_area(2), 2))
print(exists("square"))פלט:
[1] 12.57
[1] FALSEאתגר
קלהשלימו את המפעל make_account(owner) בקובץ Account.R. היתרה מתחילה ב־0, והיסטוריה פרטית מתעדת כל שינוי בתור +50 או -20. הרשימה המוחזרת כוללת ארבע פונקציות:
deposit(x)עוצרת עםamount must be positiveאלא אםxגדול מ־0;withdraw(x)עוצרת עםamount must be positiveאו עםinsufficient funds;balance()מחזירה את היתרה;history()מחזירה את ההיסטוריה בתור וקטור תווים.
בדקו לפני שמשנים משהו והשתמשו ב־call. = FALSE. הקוד שסופק קורא את שם הבעלים, ואז שורות dep 50 או wd 20, מדפיס ok או error: ואת ההודעה עבור כל אחת, מנסה לשנות עותק של ההיסטוריה, ומדפיס את היתרה ואת ההיסטוריה.
כתבו את הקוד שלכם בקובץ Account.R. הקובץ main.R מכיל את קוד הקלט/פלט שסופק, ואי אפשר לערוך אותו.
נסו בעצמכם
source("Account.R")
# קוד הקלט/פלט שסופק: יש להשאיר אותו כפי שהוא
input <- suppressWarnings(readLines(file("stdin")))
acc <- make_account(input[1])
for (line in input[-1]) {
p <- strsplit(line, " ")[[1]]
amount <- as.numeric(p[2])
msg <- tryCatch({
if (p[1] == "dep") acc$deposit(amount) else acc$withdraw(amount)
"ok"
}, error = function(e) paste("error:", conditionMessage(e)))
cat(line, ": ", msg, "\n", sep = "")
}
h <- acc$history()
h[1] <- "tampered"
cat("balance: ", acc$balance(), "\n", sep = "")
cat("history: ", paste(acc$history(), collapse = ", "), "\n", sep = "")
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות S3
עבודה עם קבציםרשימות עם מחלקהפונקציות בנאימתודות print ו-formatמאמתים ופונקציות עזרחזרה – טמפרטורותתרגלו בעצמכם: קומפיילר R אונליין