setValidity и validObject
Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 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 refusedvalidObject(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"Задание
ЛегкоКласс Account в файле Account.R имеет слоты owner и balance. Дополните файл:
- добавьте правило с
setValidity()(обёрнутым вinvisible()), которое возвращаетowner must not be emptyилиbalance must not be negative; - создайте обобщения
deposit(acc, amount)иwithdraw(acc, amount)и их методы дляAccount:depositзавершается с ошибкойamount must be positive(используйтеcall. = FALSE) для значений 0 или меньше; оба метода изменяют баланс, вызывают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
})
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы S3
Работа с файламиСписки с классомФункции-конструкторыМетоды print и formatВалидаторы и вспомогательные функцииПовторение — температуры4Инкапсуляция в R
Замыкания как объектыОкружения как объектыФункции доступаЗащита состоянияПовторение — парковочный счётчик7S4 Наследование
Расширение с помощью containscallNextMethodВиртуальные классыПроверка типов с помощью is()Повторение — смотритель зоопарка10Шаблоны проектирования, часть 2
Шаблон «Декоратор»Шаблон «Строитель»Шаблон «Состояние»Шаблон «Итератор»2Обобщённые функции и методы S3
Обобщённые функции и UseMethodМетоды по умолчаниюМетоды для базовых обобщённых функцийОсновы NextMethodПовторение — площади фигур5Классы S4
Определение классов S4Слоты и доступПроверка корректностиМетоды showПовторение — каталог товаров8Ссылочные классы
Определение ссылочных классовМетоды и обновление полейСемантика копированияНаследование и callSuperПовторение — очередь задач11Проект: Управление библиотекой
Книги и читателиВыдача книг3Наследование S3
Векторы классовПорядок поиска методовРасширение с помощью NextMethodПроверка с помощью inherits()Повторение — расчёт зарплаты сотрудников6S4: обобщения и методы
Создание обобщенийМетоды для встроенных объектовДиспетчеризация по двум аргументамsetValidity и validObjectПовторение — деньги и валютаПотренируйтесь самостоятельно: Онлайн-компилятор R