Menu
CoddyTech

保护状态

本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 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

挑战

简单

完成 Account.R 中的工厂函数 make_account(owner)。余额从 0 开始,私有历史记录会将每次变化记录为 +50 或 -20。返回的列表包含四个函数:

  • deposit(x):如果 x 不大于 0,则以 amount must be positive 终止;
  • 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 在线编译器