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

نمط البناء

جزء من قسم البرمجة كائنية التوجه في رحلة R على Coddy. الدرس 47 من 57.

يقوم المنشئ بتجميع كائن معقد خطوةً بخطوة، وينشئه في النهاية باستخدام build(). يحتفظ المنشئ القائم على الإغلاق بالأجزاء في متغيرات خاصة؛ وتُعيد كل خطوة المنشئ نفسه باستخدام invisible(self)، بحيث تتسلسل الخطوات، كما أن استدعاء خطوة بمفردها لا يطبع شيئًا:

pizza_builder <- function() {
  size <- "medium"
  toppings <- character(0)
  self <- list(
    size = function(s) { size <<- s; invisible(self) },
    add = function(t) { toppings <<- c(toppings, t); invisible(self) },
    build = function() structure(list(size = size, toppings = toppings), class = "pizza")
  )
  self
}
p <- pizza_builder()$size("large")$add("olives")$add("basil")$build()
cat(p$size, "with", paste(p$toppings, collapse = " and "))

المخرجات:

large with olives and basil

‏build() هو المكان المناسب للتحقق من اكتمال الكائن. تتوقف الأجزاء المفقودة مع ظهور رسالة بدلًا من إنتاج كائن غير مكتمل:

email_builder <- function() {
  to <- NULL
  subject <- ""
  self <- list(
    to = function(x) { to <<- x; invisible(self) },
    subject = function(x) { subject <<- x; invisible(self) },
    build = function() {
      if (is.null(to)) stop("an email needs a recipient", call. = FALSE)
      paste0("To: ", to, " | Subject: ", subject)
    }
  )
  self
}
print(email_builder()$to("ada@x.org")$subject("Hi")$build())
print(tryCatch(email_builder()$subject("Hi")$build(), error = function(e) conditionMessage(e)))

الناتج:

[1] "To: ada@x.org | Subject: Hi"
[1] "an email needs a recipient"

تعمل الفكرة نفسها مع القيم العادية ومع الأنبوب |>. كل خطوة هي دالة تأخذ الوصف حتى الآن وتُرجع نسخة محدَّثة منه، وتبني الخطوة الأخيرة النتيجة:

request <- function(url) list(url = url, method = "GET", headers = character(0))
with_method <- function(req, m) { req$method <- m; req }
with_header <- function(req, name, value) { req$headers[name] <- value; req }
build_request <- function(req) {
  c(paste(req$method, req$url), paste0(names(req$headers), ": ", req$headers))
}
r <- request("/users") |> with_method("POST") |> with_header("Accept", "json") |> build_request()
cat(r, sep = "\n")

الناتج:

POST /users
Accept: json

يستبدل المُنشئُ مُنشئًا آخرَ يحتوي على العديد من الوسائط الاختيارية. تُسمّي كل خطوة ما تضبطه، وتحافظ الأجزاء التي لم يُذكرها على قيمها الافتراضية:

# صعب القراءة: ما هو TRUE، ما هو 3؟
make_report("sales", TRUE, FALSE, 3, "pdf")

# الباني يوضح معنى كل قيمة
report_builder("sales")$with_totals()$top(3)$format("pdf")$build()
challenge icon

التحدي

سهل

أكمل query_builder(table) القائم على الإغلاق في QueryBuilder.R. تُعيد خطواته المُنشئ بحيث يمكن تسلسلها:

  • select(cols): الأعمدة التي سيتم عرضها (الافتراضي *)؛
  • where(cond): يضيف شرطًا؛ وتُضم عدة شروط باستخدام AND ؛
  • order_by(col) وlimit(n)؛
  • build(): تُعيد الاستعلام، مثل SELECT name, age FROM users WHERE age > 30 AND city = 'Rome' ORDER BY name LIMIT 5، مع حذف الأجزاء التي لم يتم تعيينها.

يقرأ الكود المُقدَّم الجدول، ثم الأسطر select name age وwhere age > 30 وorder name أو limit 5، ويطبع الاستعلام المُنشأ. ثم ينشئ استعلامًا ثانيًا في سلسلة واحدة.

ضع الكود الخاص بك في QueryBuilder.R. يحتوي main.R على كود الإدخال/الإخراج المُقدَّم ولا يمكن تعديله.

جرّب بنفسك

source("QueryBuilder.R")

# كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
input <- suppressWarnings(readLines(file("stdin")))
q <- query_builder(input[1])
for (line in input[-1]) {
  p <- strsplit(line, " ")[[1]]
  rest <- paste(p[-1], collapse = " ")
  if (p[1] == "select") q <- q$select(p[-1])
  if (p[1] == "where") q <- q$where(rest)
  if (p[1] == "order") q <- q$order_by(p[2])
  if (p[1] == "limit") q <- q$limit(as.numeric(p[2]))
}
cat(q$build(), "\n", sep = "")
cat(query_builder("logs")$select("msg")$limit(1)$build(), "\n", sep = "")
quiz iconاختبر نفسك

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

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

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