Защита состояния
Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 20 из 57.
Сеттер — это единственное место, где значение может измениться, поэтому именно здесь следует применять правила. Сначала он выполняет проверку и вызывает stop(), прежде чем что-либо изменится, поэтому объект остаётся корректным:
make_account <- function(balance = 0) {
withdraw <- function(x) {
if (x <= 0) stop("amount must be positive", call. = FALSE)
if (x > balance) stop("insufficient funds", call. = FALSE)
balance <<- balance - x
}
list(withdraw = withdraw, balance = function() balance)
}
acc <- make_account(50)
msg <- tryCatch(acc$withdraw(80), error = function(e) conditionMessage(e))
cat(msg, "/", acc$balance())Вывод:
insufficient funds / 50lockBinding("rate", e) делает одну переменную окружения доступной только для чтения. Присваивание ей после этого вызывает ошибку, а остальные переменные остаются доступными для записи:
loan <- new.env()
loan$rate <- 0.05
loan$owed <- 1000
lockBinding("rate", loan)
loan$owed <- 900
msg <- tryCatch({ loan$rate <- 0; "changed" }, error = function(e) conditionMessage(e))
cat(msg, "\n")
cat(loan$rate, loan$owed, "\n")Вывод:
cannot change value of locked binding for 'rate'
0.05 900 lockEnvironment(e) запрещает добавлять новые переменные. При использовании bindings = TRUE она также блокирует каждую существующую переменную, что делает весь объект доступным только для чтения:
cfg <- new.env()
cfg$theme <- "dark"
lockEnvironment(cfg, bindings = TRUE)
for (step in c("change", "add")) {
msg <- tryCatch({
if (step == "change") cfg$theme <- "light" else cfg$size <- 12
"done"
}, error = function(e) conditionMessage(e))
cat(step, ":", msg, "\n")
}Результат:
change : cannot change value of locked binding for 'theme'
add : cannot add bindings to a locked environment local() выполняет код в новом окружении и возвращает его последнее значение. Вспомогательные функции, определённые внутри, остаются скрытыми, благодаря чему файл может сохранять функцию приватной, даже если source() помещает всё остальное в глобальное окружение:
circle_area <- local({
square <- function(x) x * x
function(r) pi * square(r)
})
print(round(circle_area(2), 2))
print(exists("square"))Результат:
[1] 12.57
[1] FALSEЗадание
ЛегкоЗавершите фабрику make_account(owner) в Account.R. Баланс начинается с 0, а приватная история записывает каждое изменение как +50 или -20. Возвращаемый список содержит четыре функции:
deposit(x)завершается с ошибкойamount must be positive, если толькоxне больше 0;withdraw(x)завершается с ошибкойamount must be positiveилиinsufficient funds;balance()возвращает баланс;history()возвращает историю как вектор символов.
Проверяйте всё до внесения изменений и используйте call. = FALSE. Предоставленный код считывает владельца, затем строки dep 50 или wd 20, выводит ok или error: и сообщение для каждой строки, пытается изменить копию истории, а затем выводит баланс и историю.
Ваш код должен находиться в Account.R. В main.R содержится предоставленный код ввода/вывода, и его нельзя редактировать.
Попробуйте сами
source("Account.R")
# Предоставленный код ввода/вывода: оставьте его как есть
input <- suppressWarnings(readLines(file("stdin")))
acc <- make_account(input[1])
for (line in input[-1]) {
p <- strsplit(line, " ")[[1]]
amount <- as.numeric(p[2])
msg <- tryCatch({
if (p[1] == "dep") acc$deposit(amount) else acc$withdraw(amount)
"ok"
}, error = function(e) paste("error:", conditionMessage(e)))
cat(line, ": ", msg, "\n", sep = "")
}
h <- acc$history()
h[1] <- "tampered"
cat("balance: ", acc$balance(), "\n", sep = "")
cat("history: ", paste(acc$history(), collapse = ", "), "\n", sep = "")
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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()Повторение — расчёт зарплаты сотрудниковПотренируйтесь самостоятельно: Онлайн-компилятор R