Шаблон «Строитель»
Часть раздела Объектно-ориентированное программирование путешествия по 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 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 и выводит построенный запрос. Затем он создаёт второй запрос в одной цепочке.
Ваш код должен находиться в 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 = "")
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы S3
Работа с файламиСписки с классомФункции-конструкторыМетоды print и formatВалидаторы и вспомогательные функцииПовторение — температуры4Инкапсуляция в R
Замыкания как объектыОкружения как объектыФункции доступаЗащита состоянияПовторение — парковочный счётчик7S4 Наследование
Расширение с помощью containscallNextMethodВиртуальные классыПроверка типов с помощью is()Повторение — смотритель зоопарка10Шаблоны проектирования, часть 2
Шаблон «Декоратор»Шаблон «Строитель»Шаблон «Состояние»Шаблон «Итератор»2Обобщённые функции и методы S3
Обобщённые функции и UseMethodМетоды по умолчаниюМетоды для базовых обобщённых функцийОсновы NextMethodПовторение — площади фигур5Классы S4
Определение классов S4Слоты и доступПроверка корректностиМетоды showПовторение — каталог товаров8Ссылочные классы
Определение ссылочных классовМетоды и обновление полейСемантика копированияНаследование и callSuperПовторение — очередь задач11Проект: Управление библиотекой
Книги и читателиВыдача книг3Наследование S3
Векторы классовПорядок поиска методовРасширение с помощью NextMethodПроверка с помощью inherits()Повторение — расчёт зарплаты сотрудниковПотренируйтесь самостоятельно: Онлайн-компилятор R