Menu
Coddy logo textTech

Protegendo o Estado

Parte da seção Programação Orientada a Objetos do Journey de R da Coddy. Lição 20 de 57.

Um setter é o único lugar onde um valor pode mudar, portanto é nele que as regras devem ser impostas. Ele verifica primeiro e chama stop() antes que qualquer coisa seja alterada, mantendo o objeto válido:

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

Saída:

insufficient funds / 50

lockBinding("rate", e) torna uma variável de um ambiente somente leitura. Atribuir um valor a ela depois disso gera um erro, enquanto as outras variáveis continuam podendo ser alteradas:

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

Saída:

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

lockEnvironment(e) impede a adição de novas variáveis. Com bindings = TRUE, ele também bloqueia todas as existentes, o que torna o objeto inteiro somente leitura:

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

Saída:

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

local() executa o código em um ambiente novo e retorna seu último valor. Os auxiliares definidos dentro dele permanecem ocultos, e é assim que um arquivo pode manter uma função privada, mesmo que source() coloque todo o restante no ambiente global:

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

Saída:

[1] 12.57
[1] FALSE
challenge icon

Desafio

Fácil

Complete a fábrica make_account(owner) em Account.R. O saldo começa em 0 e um histórico privado registra cada alteração como +50 ou -20. A lista retornada tem quatro funções:

  • deposit(x) interrompe com amount must be positive, a menos que x seja maior que 0;
  • withdraw(x) interrompe com amount must be positive ou insufficient funds;
  • balance() retorna o saldo;
  • history() retorna o histórico como um vetor de caracteres.

Verifique antes de alterar qualquer coisa e use call. = FALSE. O código fornecido lê o proprietário, depois linhas dep 50 ou wd 20, imprime ok ou error: e a mensagem correspondente para cada uma, tenta alterar uma cópia do histórico e imprime o saldo e o histórico.

Seu código deve ser colocado em Account.R. main.R contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

source("Account.R")

# Código de entrada/saída fornecido: mantenha-o como está
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 iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Compilador de R online