Menu
CoddyTech

setValidity và validObject

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 30 trên 57.

setValidity("Class", function(object) ...) thêm quy tắc hợp lệ sau khi khai báo lớp, giúp lời gọi setClass() dài gọn hơn. Giống như setGeneric(), hàm này trả về một giá trị được in ở cấp cao nhất, vì vậy hãy bọc nó trong 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)

Đầu ra:

invalid class “Account” object: balance must not be negative

Các thay đổi nên được thực hiện thông qua những phương thức kiểm tra kết quả. Phương thức withdraw() thay đổi giá trị trong slot, gọi validObject() và chỉ trả về đối tượng khi đối tượng đó vẫn hợp lệ:

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)

Đầu ra:

refused 30

Một cặp phương thức truy cập có thể dùng generic: balance(x) đọc giá trị, còn generic thay thế "balance<-" ghi giá trị. Phương thức này kiểm tra đối tượng, nên balance(a) <- -5 bị từ chối như mọi thay đổi không hợp lệ khác:

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)

Đầu ra:

25 refused

validObject(x, test = TRUE) không dừng lại. Hàm trả về TRUE nếu đối tượng hợp lệ, hoặc nội dung mô tả vấn đề, rất hữu ích khi tạo báo cáo:

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

Đầu ra:

[1] TRUE
[1] "w must be positive"
challenge icon

Thử thách

Dễ

Lớp Account trong Account.R có các khe owner và balance. Hoàn thiện tệp:

  • thêm một quy tắc bằng setValidity() (bọc trong invisible()) trả về owner must not be empty hoặc balance must not be negative;
  • tạo các hàm generic deposit(acc, amount) và withdraw(acc, amount) cùng các phương thức Account tương ứng: deposit dừng với amount must be positive (dùng call. = FALSE) nếu số tiền bằng 0 hoặc nhỏ hơn; cả hai đều thay đổi số dư, gọi validObject() và trả về tài khoản;
  • thêm phương thức show() để in ra ada: 50 và một dòng mới.

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, rồi in tài khoản sau mỗi lần thay đổi hoặc in refused: cùng với vấn đề.

Code của bạn nằm trong Account.R. main.R chứa code đầu vào/đầu ra được cung cấp và không thể chỉnh sửa.

Tự làm thử

source("Account.R")

# Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên như hiện tại
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
    })
  }
}
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