Menu
CoddyTech

Mẫu Builder

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

Một builder tạo một đối tượng phức tạp theo từng bước và hoàn tất việc tạo đối tượng ở cuối bằng build(). Builder dựa trên closure giữ các phần trong biến riêng tư; mỗi bước trả về chính builder bằng invisible(self), nhờ đó các bước có thể nối tiếp nhau và một bước được gọi riêng sẽ không in ra gì:

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

Đầu ra:

large with olives and basil

build() là nơi kiểm tra đối tượng đã hoàn chỉnh chưa. Nếu thiếu phần nào, chương trình sẽ dừng và hiển thị thông báo thay vì tạo ra một đối tượng chưa hoàn chỉnh:

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

Đầu ra:

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

Ý tưởng tương tự cũng áp dụng với các giá trị thông thường và toán tử pipe |>. Mỗi bước là một hàm nhận mô tả hiện tại và trả về một bản sao đã cập nhật, còn bước cuối cùng tạo ra kết quả:

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

Đầu ra:

POST /users
Accept: json

Một builder thay thế hàm khởi tạo có nhiều đối số tùy chọn. Mỗi bước nêu rõ giá trị mà nó thiết lập, còn những phần không được nhắc đến sẽ giữ nguyên giá trị mặc định:

# Khó hiểu: TRUE và 3 có nghĩa là gì?
make_report("sales", TRUE, FALSE, 3, "pdf")

# Builder cho biết ý nghĩa của từng giá trị (value)
report_builder("sales")$with_totals()$top(3)$format("pdf")$build()
challenge icon

Thử thách

Dễ

Hoàn thiện query_builder(table) dựa trên closure trong QueryBuilder.R. Các bước của hàm trả về bộ dựng để bạn có thể nối chuỗi lệnh:

  • select(cols): các cột cần hiển thị (mặc định là *);
  • where(cond): thêm một điều kiện; nếu có nhiều điều kiện, hãy nối chúng bằng AND ;
  • order_by(col) và limit(n);
  • build(): trả về truy vấn, chẳng hạn SELECT name, age FROM users WHERE age > 30 AND city = 'Rome' ORDER BY name LIMIT 5, đồng thời bỏ qua những phần chưa được thiết lập.

Code được cung cấp đọc bảng, sau đó đọc các dòng select name age, where age > 30, order name hoặc limit 5, rồi in ra truy vấn đã dựng. Tiếp đó, code dựng truy vấn thứ hai bằng một chuỗi lệnh.

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

Tự làm thử

source("QueryBuilder.R")

# Đoạn code đầu vào/đầu ra đã cho: giữ nguyên
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 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