Menu
CoddyTech

使用 NextMethod 扩展

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

子类方法通常会完成父类的工作,再多做一点。NextMethod() 会调用父类方法,因此子类只需添加新的内容。经理的月薪等于员工的工资加上奖金:

pay <- function(x) UseMethod("pay")
pay.employee <- function(x) x$salary / 12
pay.manager <- function(x) NextMethod() + x$bonus / 12
m <- structure(list(salary = 60000, bonus = 12000), class = c("manager", "employee"))
e <- structure(list(salary = 36000), class = "employee")
cat(pay(e), pay(m))

输出:

3000 6000

NextMethod() 会继续向下逐层调用。每个方法都会添加自己的部分,并将处理交给它之后的类:

pay <- function(x) UseMethod("pay")
pay.employee <- function(x) x$salary / 12
pay.manager <- function(x) NextMethod() + x$bonus / 12
pay.director <- function(x) NextMethod() + 500
d <- structure(list(salary = 120000, bonus = 24000),
               class = c("director", "manager", "employee"))
print(pay(d))

输出:

[1] 12500

子类方法也可以先进行检查,只有检查通过后才交给父类方法处理。父类的方法会返回修改后的对象,并保留完整的类向量,所以结果仍然是一个储蓄账户:

withdraw <- function(acc, amount) UseMethod("withdraw")
withdraw.account <- function(acc, amount) {
  if (amount > acc$balance) stop("insufficient funds", call. = FALSE)
  acc$balance <- acc$balance - amount
  acc
}
withdraw.savings <- function(acc, amount) {
  if (amount > 500) stop("savings limit is 500", call. = FALSE)
  NextMethod()
}
s <- structure(list(balance = 1000), class = c("savings", "account"))
s <- withdraw(s, 200)
cat(s$balance, class(s))

输出:

800 savings account

构造函数和方法以相同的方式复用父类:new_manager() 调用 new_employee(),而 pay.manager() 调用 NextMethod()。员工代码只需编写一次,每个子类都在此基础上构建:

new_manager <- function(name, salary, bonus) {
  new_employee(name, salary, bonus = bonus, class = "manager")   # 复用构造函数
}
pay.manager <- function(x) NextMethod() + x$bonus / 12            # 复用方法
challenge icon

挑战

简单

账户和储蓄账户分别放在两个文件中。在 Account.R 中完成 withdraw.account(acc, amount):当金额高于余额时,使用 insufficient funds 停止;否则返回余额较低的账户。还要完成 format.account(),使其返回 ada: 800。在 Savings.R 中完成 withdraw.savings():当金额高于 500 时,使用 savings limit is 500 停止;否则调用 NextMethod() 继续处理。还要完成 format.savings(),通过调用 NextMethod() 返回 ada: 800 (savings)。

使用 call. = FALSE。泛型函数和构造函数已提供。给定的代码会读取 savings ada 1000 或 account bo 300,然后读取要取出的金额;它会输出 withdrew 200 或 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") new_savings(p[2], as.numeric(p[3])) else new_account(p[2], as.numeric(p[3]))
for (amount in as.numeric(input[-1])) {
  acc <- tryCatch({
    acc <- withdraw(acc, amount)
    cat("withdrew ", amount, "\n", sep = "")
    acc
  }, error = function(e) {
    cat("refused: ", conditionMessage(e), "\n", sep = "")
    acc
  })
}
cat(format(acc), "\n", sep = "")
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: R 在线编译器