建造者模式
本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 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 basilbuild() 是检查对象是否完整的地方。缺少的部分会导致程序停止并显示一条消息,而不是生成一个不完整的对象:
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")
# builder 能说明每个值的含义
report_builder("sales")$with_totals()$top(3)$format("pdf")$build()挑战
简单完成基于闭包的 query_builder(table)。它的各个步骤都会返回构建器,因此可以链式调用:
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 = "")
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: R 在线编译器