Menu
Coddy logo textTech

setValidityとvalidObject

CoddyのRジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 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 refused

validObject(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"
challenge icon

チャレンジ

簡単

Account.R のクラス Account には、スロット owner と balance があります。ファイルを完成させてください:

  • setValidity()(invisible() でラップ)を使って、owner must not be empty または balance must not be negative を返すルールを追加する;
  • ジェネリック関数 deposit(acc, amount) と withdraw(acc, amount)、およびそれらの Account メソッドを作成する:deposit は 0 以下の金額に対して amount must be positive で停止し(call. = FALSE を使用)、両方とも残高を変更し、validObject() を呼び出して、アカウントを返す;
  • ada: 50 と改行を出力する show() メソッドを追加する。

提供されたコードは所有者を読み取り、その後に続く 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
    })
  }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rオンラインコンパイラ