Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

حماية الحالة

جزء من قسم البرمجة كائنية التوجه في رحلة 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 / 50

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

التحدي

سهل

أكمل المصنع 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 = "")
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم R عبر الإنترنت