Menu
Coddy logo textTech

Расширение с помощью NextMethod

Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 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