Menu
Coddy logo textTech

Dekorierer-Muster

Teil des Abschnitts Objektorientierte Programmierung der R-Journey von Coddy. Lektion 46 von 57.

Ein Dekorator umschließt etwas und fügt Verhalten hinzu, während dieselbe Schnittstelle beibehalten wird. Bei einer Funktion nimmt der Dekorator eine Funktion entgegen und gibt eine neue Funktion zurück, die zusätzliche Arbeit rund um den Aufruf erledigt:

with_logging <- function(f, name) {
  function(x) {
    cat("calling", name, "with", x, "\n")
    f(x)
  }
}
double <- function(x) x * 2
logged <- with_logging(double, "double")
print(logged(21))

Ausgabe:

calling double with 21 
[1] 42

Ein Dekorator kann Ergebnisse speichern. with_cache() bewahrt jede Antwort in einer Umgebung auf und ruft die umschlossene Funktion nur für neue Argumente auf:

with_cache <- function(f) {
  cache <- new.env()
  function(x) {
    key <- as.character(x)
    if (!exists(key, envir = cache, inherits = FALSE)) assign(key, f(x), envir = cache)
    get(key, envir = cache)
  }
}
work <- 0
slow_square <- function(x) {
  work <<- work + 1
  x^2
}
fast <- with_cache(slow_square)
for (x in c(3, 4, 3, 3, 4)) fast(x)
cat("results computed:", work)

Ausgabe:

results computed: 2

Auch Objekte können dekoriert werden. Jeder Decorator ist ein Objekt, das das von ihm umhüllte Objekt in einem Feld enthält und auf dieselben Generics reagiert, seinen Teil hinzufügt und das innere Objekt nach dem Rest fragt:

cost <- function(d) UseMethod("cost")
describe <- function(d) UseMethod("describe")
cost.coffee <- function(d) 2
describe.coffee <- function(d) "coffee"
with_milk <- function(inner) structure(list(inner = inner), class = "milk")
cost.milk <- function(d) cost(d$inner) + 0.5
describe.milk <- function(d) paste(describe(d$inner), "+ milk")
cup <- with_milk(with_milk(structure(list(), class = "coffee")))
cat(describe(cup), "=", cost(cup))

Ausgabe:

coffee + milk + milk = 3

Dekoratoren werden verschachtelt, und ihre Reihenfolge ist wichtig: Der äußerste wird beim Hineinweg zuerst und beim Herausweg zuletzt ausgeführt:

exclaim <- function(f) function(s) paste0(f(s), "!")
twice <- function(f) function(s) paste(f(s), f(s))
greet <- function(name) paste("hi", name)
a <- twice(exclaim(greet))
b <- exclaim(twice(greet))
cat(a("ada"), "/", b("ada"))

Ausgabe:

hi ada! hi ada! / hi ada hi ada!
challenge icon

Aufgabe

Einfach

Getränke werden mit Extras verfeinert. Drink.R enthält die generischen Funktionen cost() und describe() sowie das Basisgetränk new_coffee(), das 2 kostet und sich als coffee beschreibt. Schreibe in Extras.R drei Dekorierer, von denen jeder ein Objekt ist, das das von ihm umschlossene Getränk im Feld inner enthält:

  • add_milk(d): Klasse milk, addiert 0.50 und + milk;
  • add_sugar(d): Klasse sugar, addiert 0.20 und + sugar;
  • add_syrup(d, flavour): Klasse syrup mit dem Feld flavour, addiert 0.80 und + vanilla syrup.

Schreibe ihre cost()- und describe()-Methoden, indem du zuerst das innere Getränk fragst. Der bereitgestellte Code liest Zeilen wie milk,syrup vanilla,sugar (eine leere Zeile entspricht einem einfachen Kaffee) und gibt coffee + milk + vanilla syrup + sugar: 3.50 aus.

Dein Code gehört in Drink.R und Extras.R. main.R enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

source("Drink.R")
source("Extras.R")

# Bereitgestellter Ein-/Ausgabe-Code: belasse ihn wie er ist
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  d <- new_coffee()
  if (nchar(line) > 0) for (extra in strsplit(line, ",")[[1]]) {
    p <- strsplit(extra, " ")[[1]]
    d <- switch(p[1], milk = add_milk(d), sugar = add_sugar(d), syrup = add_syrup(d, p[2]))
  }
  cat(sprintf("%s: %.2f", describe(d), cost(d)), "\n", sep = "")
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-R-Compiler