Menu
Coddy logo textTech

הגנה על מצב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 / 50

lockBinding("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
challenge icon

אתגר

קל

השלימו את המפעל 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 = "")
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר R אונליין