Menu
CoddyTech

Bảo vệ trạng thái

Thuộc phần Lập trình hướng đối tượng trong hành trình R của Coddy. Bài học 20 trên 57.

Phương thức setter là nơi duy nhất giá trị có thể thay đổi, vì vậy đây là nơi thực thi các quy tắc. Phương thức này kiểm tra trước và gọi stop() trước khi có bất kỳ thay đổi nào, nhờ đó đối tượng luôn hợp lệ:

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())

Đầu ra:

insufficient funds / 50

lockBinding("rate", e) khiến một biến trong môi trường trở thành chỉ đọc. Gán giá trị cho biến đó sau này sẽ gây ra lỗi, còn các biến khác vẫn có thể ghi được:

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")

Đầu ra:

cannot change value of locked binding for 'rate' 
0.05 900 

lockEnvironment(e) ngăn thêm biến mới. Với bindings = TRUE, hàm này cũng khóa mọi biến hiện có, khiến toàn bộ đối tượng chỉ có thể đọc:

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")
}

Đầu ra:

change : cannot change value of locked binding for 'theme' 
add : cannot add bindings to a locked environment 

local() chạy code trong một môi trường mới và trả về giá trị cuối cùng. Các hàm trợ giúp được định nghĩa bên trong sẽ không hiển thị ra bên ngoài. Nhờ vậy, một tệp có thể giữ riêng tư một hàm, dù source() đưa mọi thứ khác vào môi trường toàn cục:

circle_area <- local({
  square <- function(x) x * x
  function(r) pi * square(r)
})
print(round(circle_area(2), 2))
print(exists("square"))

Đầu ra:

[1] 12.57
[1] FALSE
challenge icon

Thử thách

Dễ

Hoàn thiện hàm tạo make_account(owner) trong Account.R. Số dư bắt đầu từ 0 và lịch sử riêng ghi lại mọi thay đổi dưới dạng +50 hoặc -20. Danh sách được trả về có bốn hàm:

  • deposit(x) dừng với lỗi amount must be positive nếu x không lớn hơn 0;
  • withdraw(x) dừng với lỗi amount must be positive hoặc insufficient funds;
  • balance() trả về số dư;
  • history() trả về lịch sử dưới dạng một vector ký tự.

Kiểm tra trước khi thay đổi bất cứ điều gì và dùng call. = FALSE. Code được cung cấp đọc tên chủ tài khoản, sau đó đọc các dòng dep 50 hoặc wd 20, in ra ok hoặc error: cùng thông báo tương ứng cho mỗi dòng, thử thay đổi một bản sao của lịch sử, rồi in số dư và lịch sử.

Hãy viết code trong Account.R. main.R chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

source("Account.R")

# Đoạn code đầu vào/đầu ra đã cho: giữ nguyên
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 = "")
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch R online