Builder-Muster
Teil des Abschnitts Objektorientierte Programmierung der R-Journey von Coddy. Lektion 47 von 57.
Ein Builder setzt ein kompliziertes Objekt Schritt für Schritt zusammen und erstellt es am Ende mit build(). Ein auf Closures basierender Builder hält die Bestandteile in privaten Variablen; jeder Schritt gibt den Builder selbst mit invisible(self) zurück, sodass die Schritte verkettet werden und ein einzeln aufgerufener Schritt nichts ausgibt:
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 "))Ausgabe:
large with olives and basilbuild() ist der Ort, an dem geprüft wird, ob das Objekt vollständig ist. Fehlende Teile führen zu einer Meldung, statt ein halb erstelltes Objekt zu erzeugen:
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)))Ausgabe:
[1] "To: ada@x.org | Subject: Hi"
[1] "an email needs a recipient"Dieselbe Idee funktioniert mit einfachen Werten und der Pipe |>. Jeder Schritt ist eine Funktion, die die bisherige Beschreibung entgegennimmt und eine aktualisierte Kopie zurückgibt, und der letzte Schritt erstellt das Ergebnis:
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")Ausgabe:
POST /users
Accept: jsonEin Builder ersetzt einen Konstruktor mit vielen optionalen Argumenten. Jeder Schritt benennt, was er festlegt, und nicht erwähnte Teile behalten ihre Standardwerte:
# schwer zu lesen: was ist TRUE, was ist 3?
make_report("sales", TRUE, FALSE, 3, "pdf")
# ein Builder sagt, was jeder Wert bedeutet
report_builder("sales")$with_totals()$top(3)$format("pdf")$build()Aufgabe
EinfachVervollständige den auf Closures basierenden query_builder(table) in QueryBuilder.R. Seine Schritte geben den Builder zurück, sodass sie verkettet werden können:
select(cols): die anzuzeigenden Spalten (Standardwert*);where(cond): fügt eine Bedingung hinzu; mehrere werden mitANDverbunden;order_by(col)undlimit(n);build(): gibt die Abfrage zurück, beispielsweiseSELECT name, age FROM users WHERE age > 30 AND city = 'Rome' ORDER BY name LIMIT 5, wobei die nicht gesetzten Teile weggelassen werden.
Der bereitgestellte Code liest die Tabelle und anschließend die Zeilen select name age, where age > 30, order name oder limit 5 und gibt die erstellte Abfrage aus. Danach erstellt er eine zweite Abfrage in einer einzigen Verkettung.
Dein Code gehört in QueryBuilder.R. main.R enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.
Probier es selbst
source("QueryBuilder.R")
# Gelieferter Ein-/Ausgabecode: belasse ihn wie er ist
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 = "")
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1S3-Grundlagen
Arbeiten mit DateienListen mit einer KlasseKonstruktorfunktionenPrint- und Format-MethodenValidatoren und HilfsfunktionenRückblick – Temperaturen4Kapselung in R
Closures als ObjekteUmgebungen als ObjekteZugriffsfunktionenZustand schützenRückblick – Parkuhr2S3-Generics und Methoden
Generics und UseMethodStandardmethodenMethoden für Basis-GenericsGrundlagen von NextMethodWiederholung – Flächeninhalte von Formen5S4-Klassen
S4-Klassen definierenSlots und ZugriffGültigkeitsprüfungenShow-MethodenRückblick – Produktkatalog8Referenzklassen
Referenzklassen definierenMethoden und FeldänderungenKopiersemantikVererbung und callSuperZusammenfassung – Aufgabenwarteschlange11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Online-R-Compiler