Menu
CoddyTech

setValidity 和 validObject

本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 30 课,共 57 课。

setValidity("Class", function(object) ...) 会在类声明后添加有效性规则,这样可以让较长的 setClass() 调用保持简短。和 setGeneric() 一样,它会返回一个在顶层打印的值,因此要用 invisible() 包起来:

setClass("Account", slots = c(owner = "character", balance = "numeric"))
invisible(setValidity("Account", function(object) {
  if (object@balance < 0) return("balance must not be negative")
  TRUE
}))
msg <- tryCatch(new("Account", owner = "Ada", balance = -1), error = function(e) conditionMessage(e))
cat(msg)

输出:

invalid class “Account” object: balance must not be negative

更改应通过会检查结果的方法进行。withdraw() 方法会修改槽位,调用 validObject(),并且只在对象仍然有效时返回该对象:

setClass("Account", slots = c(balance = "numeric"))
invisible(setValidity("Account", function(object) {
  if (object@balance < 0) "balance must not be negative" else TRUE
}))
invisible(setGeneric("withdraw", function(acc, amount) standardGeneric("withdraw")))
setMethod("withdraw", "Account", function(acc, amount) {
  acc@balance <- acc@balance - amount
  validObject(acc)
  acc
})
a <- new("Account", balance = 50)
a <- withdraw(a, 20)
msg <- tryCatch({ a <- withdraw(a, 100); "done" }, error = function(e) "refused")
cat(msg, a@balance)

输出:

refused 30

访问器对可以由泛型构成:balance(x) 用于读取,替换泛型 "balance<-" 用于写入。它的方法会检查对象,因此像其他无效修改一样,balance(a) <- -5 会被拒绝:

setClass("Account", slots = c(balance = "numeric"),
  validity = function(object) if (object@balance < 0) "balance must not be negative" else TRUE)
invisible(setGeneric("balance", function(x) standardGeneric("balance")))
invisible(setGeneric("balance<-", function(x, value) standardGeneric("balance<-")))
setMethod("balance", "Account", function(x) x@balance)
setMethod("balance<-", "Account", function(x, value) {
  x@balance <- value
  validObject(x)
  x
})
a <- new("Account", balance = 10)
balance(a) <- 25
msg <- tryCatch({ balance(a) <- -5; "changed" }, error = function(e) "refused")
cat(balance(a), msg)

输出:

25 refused

validObject(x, test = TRUE) 不会停止。对于有效对象,它会返回 TRUE;否则会返回问题文本,这对于生成报告很方便:

setClass("Box", slots = c(w = "numeric"),
  validity = function(object) if (object@w <= 0) "w must be positive" else TRUE)
b <- new("Box", w = 2)
print(validObject(b, test = TRUE))
b@w <- 0
print(validObject(b, test = TRUE))

输出:

[1] TRUE
[1] "w must be positive"
challenge icon

挑战

简单

Account.R 中的类 Account 有 owner 和 balance 这两个槽。请完成该文件:

  • 添加一条使用 setValidity() 的规则(用 invisible() 包装),返回 owner must not be empty 或 balance must not be negative;
  • 创建泛型 deposit(acc, amount) 和 withdraw(acc, amount),以及它们的 Account 方法:当金额小于或等于 0 时,deposit 会以 amount must be positive 终止(使用 call. = FALSE);这两个方法都会修改余额、调用 validObject() 并返回账户;
  • 添加一个 show() 方法,打印 ada: 50 并换行。

提供的代码会读取所有者,然后读取 dep 50 或 wd 20 这样的行,并在每次修改后打印账户,或者打印 refused: 和问题说明。

你的代码要写在 Account.R 中。main.R 包含提供的输入/输出代码,不能修改。

亲手试试

source("Account.R")

# 提供的输入输出代码:保持原样
input <- suppressWarnings(readLines(file("stdin")))
acc <- tryCatch(new("Account", owner = input[1], balance = 0),
                error = function(e) { cat("error: ", sub(".*object: ", "", conditionMessage(e)), "\n", sep = ""); NULL })
if (!is.null(acc)) {
  for (line in input[-1]) {
    p <- strsplit(line, " ")[[1]]
    amount <- as.numeric(p[2])
    acc <- tryCatch({
      changed <- if (p[1] == "dep") deposit(acc, amount) else withdraw(acc, amount)
      print(changed)
      changed
    }, error = function(e) {
      cat("refused: ", sub(".*object: ", "", conditionMessage(e)), "\n", sep = "")
      acc
    })
  }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: R 在线编译器