Menu
CoddyTech

继承与 callSuper

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

contains = "Account" 会让引用类继承 Account 的所有字段和方法。子类会添加自己的字段和方法:

Account <- suppressMessages(setRefClass("Account",
  fields = list(owner = "character", balance = "numeric"),
  methods = list(deposit = function(x) balance <<- balance + x)
))
Savings <- suppressMessages(setRefClass("Savings", contains = "Account",
  fields = list(rate = "numeric"),
  methods = list(add_interest = function() deposit(balance * rate))
))
s <- Savings$new(owner = "Ada", balance = 100, rate = 0.05)
s$deposit(100)
s$add_interest()
cat(s$balance, is(s, "Account"))

输出:

210 TRUE

子类通过定义一个同名方法来覆盖父类的方法。在其中,callSuper(...) 会调用父类版本,因此子类只需添加新的内容:

Account <- suppressMessages(setRefClass("Account",
  fields = list(balance = "numeric"),
  methods = list(withdraw = function(x) {
    if (x > balance) stop("insufficient funds", call. = FALSE)
    balance <<- balance - x
  })
))
Savings <- suppressMessages(setRefClass("Savings", contains = "Account",
  methods = list(withdraw = function(x) {
    if (x > 500) stop("limit is 500", call. = FALSE)
    callSuper(x)
  })
))
s <- Savings$new(balance = 300)
s$withdraw(200)
for (x in c(600, 400)) cat(tryCatch({ s$withdraw(x); "ok" }, error = function(e) conditionMessage(e)), "\n")
cat(s$balance, "\n")

输出:

limit is 500 
insufficient funds 
100 

initialize 和任何方法一样,会被继承和重写。子类的 initialize 会调用 callSuper(...),让父类设置它的字段,然后设置自己的默认值:

Account <- suppressMessages(setRefClass("Account",
  fields = list(owner = "character", balance = "numeric"),
  methods = list(initialize = function(...) {
    initFields(...)
    if (length(balance) == 0) balance <<- 0
    invisible(.self)
  })
))
Savings <- suppressMessages(setRefClass("Savings", contains = "Account",
  fields = list(rate = "numeric"),
  methods = list(initialize = function(...) {
    callSuper(...)
    if (length(rate) == 0) rate <<- 0.01
    invisible(.self)
  })
))
s <- Savings$new(owner = "Bo")
cat(s$owner, s$balance, s$rate)

输出:

Bo 0 0.01

不同子类的对象会以各自的方式响应同一个方法调用,因此遍历混合列表时不需要检查类:

Account <- suppressMessages(setRefClass("Account",
  fields = list(balance = "numeric"),
  methods = list(month_end = function() balance <<- balance - 2)
))
Savings <- suppressMessages(setRefClass("Savings", contains = "Account",
  methods = list(month_end = function() balance <<- balance * 1.01)
))
bank <- list(Account$new(balance = 100), Savings$new(balance = 100))
for (a in bank) a$month_end()
cat(vapply(bank, function(a) a$balance, numeric(1)))

输出:

98 101
challenge icon

挑战

简单

账户和储蓄账户是两个文件中的引用类。请完成这些方法:

  • Account 在 Account.R 中(字段为 owner、balance):如果没有提供余额,initialize 会将余额设为 0;deposit(x);withdraw(x) 会以 insufficient funds 终止;month_end() 会收取 1 的费用;describe() 返回 ada: 99。
  • Savings 在 Savings.R 中(包含 Account,字段为 rate):当金额超过 500 时,withdraw(x) 会以 limit is 500 终止,否则调用 callSuper(x);month_end() 会添加利息 balance * rate,而不是收取费用;describe() 会通过 callSuper() 添加 (savings)。

使用 call. = FALSE。两个定义都用 suppressMessages() 包裹。提供的代码会读取 savings ada 0.1 或 account bo,然后读取 dep 100、wd 30 或 month,并在每次操作后打印 describe(),或者打印 refused: 和错误消息。

你的代码应写在 Account.R 和 Savings.R 中。main.R 包含提供的输入/输出代码,不能编辑。

亲手试试

source("Account.R")
source("Savings.R")

# 提供的输入/输出代码:保持原样。
input <- suppressWarnings(readLines(file("stdin")))
p <- strsplit(input[1], " ")[[1]]
acc <- if (p[1] == "savings") Savings$new(owner = p[2], rate = as.numeric(p[3])) else Account$new(owner = p[2])
for (line in input[-1]) {
  q <- strsplit(line, " ")[[1]]
  msg <- tryCatch({
    if (q[1] == "dep") acc$deposit(as.numeric(q[2]))
    if (q[1] == "wd") acc$withdraw(as.numeric(q[2]))
    if (q[1] == "month") acc$month_end()
    acc$describe()
  }, error = function(e) paste("refused:", conditionMessage(e)))
  cat(line, ": ", msg, "\n", sep = "")
}
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: R 在线编译器