Builderパターン
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")
# ビルダーは各値の意味を示す
report_builder("sales")$with_totals()$top(3)$format("pdf")$build()チャレンジ
簡単クロージャーベースの 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 の行を読み込み、構築したクエリを出力します。その後、1つのチェーンで2つ目のクエリを構築します。
コードは 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オンラインコンパイラ