Menu
Coddy logo textTech

Patrón Builder

Parte de la sección Programación orientada a objetos del Journey de R de Coddy. Lección 47 de 57.

Un builder ensambla un objeto complicado paso a paso y lo crea al final con build(). Un builder basado en cierres mantiene las partes en variables privadas; cada paso devuelve el propio builder con invisible(self), por lo que los pasos se encadenan y un paso llamado por sí solo no imprime nada:

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

Salida:

large with olives and basil

build() es el lugar donde se comprueba que el objeto está completo. Las partes que faltan detienen el proceso con un mensaje en lugar de producir un objeto a medio construir:

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

Salida:

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

La misma idea funciona con valores simples y el operador de tubería |>. Cada paso es una función que toma la descripción disponible hasta ese momento y devuelve una copia actualizada, y el último paso construye el resultado:

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

Salida:

POST /users
Accept: json

Un constructor reemplaza a un constructor con muchos argumentos opcionales. Cada paso indica qué establece, y las partes que no se mencionan conservan sus valores predeterminados:

# difícil de leer: ¿qué es TRUE, qué es 3?
make_report("sales", TRUE, FALSE, 3, "pdf")

# un builder dice lo que significa cada valor
report_builder("sales")$with_totals()$top(3)$format("pdf")$build()
challenge icon

Desafío

Fácil

Completa la query_builder(table) basada en clausuras en QueryBuilder.R. Sus pasos devuelven el generador para que puedan encadenarse:

  • select(cols): las columnas que se mostrarán (predeterminado *);
  • where(cond): añade una condición; varias se unen con AND ;
  • order_by(col) y limit(n);
  • build(): devuelve la consulta, como SELECT name, age FROM users WHERE age > 30 AND city = 'Rome' ORDER BY name LIMIT 5, omitiendo las partes que no se hayan establecido.

El código proporcionado lee la tabla, después las líneas select name age, where age > 30, order name o limit 5, e imprime la consulta generada. Después genera una segunda consulta en una sola cadena.

Tu código debe estar en QueryBuilder.R. main.R contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

source("QueryBuilder.R")

# Código de entrada/salida suministrado: déjalo tal como está
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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de R online