Menu
Coddy logo textTech

Patron Décorateur

Fait partie de la section Programmation orientée objet du Journey R de Coddy. Leçon 46 sur 57.

Un décorateur encapsule quelque chose et ajoute un comportement tout en conservant la même interface. Pour une fonction, le décorateur prend une fonction et renvoie une nouvelle fonction qui effectue un travail supplémentaire autour de l’appel :

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

Sortie :

calling double with 21 
[1] 42

Un décorateur peut mémoriser les résultats. with_cache() conserve chaque réponse dans un environnement et n’appelle la fonction enveloppée que pour les nouveaux arguments :

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)

Sortie :

results computed: 2

Les objets peuvent aussi être décorés. Chaque décorateur est un objet qui contient l’objet qu’il enveloppe dans un champ et répond aux mêmes génériques, en ajoutant sa partie et en demandant le reste à l’objet interne :

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

Sortie :

coffee + milk + milk = 3

Les décorateurs s’empilent et leur ordre est important : le décorateur le plus externe s’exécute en premier à l’entrée et en dernier à la sortie :

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

Sortie :

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

Défi

Facile

Les boissons sont agrémentées d’extras. Drink.R contient les méthodes génériques cost() et describe(), ainsi que la boisson de base, new_coffee(), qui coûte 2 et se décrit comme coffee. Dans Extras.R, écrivez trois décorateurs, chacun étant un objet qui contient la boisson qu’il enveloppe dans le champ inner :

  • add_milk(d) : classe milk, ajoute 0.50 et + milk ;
  • add_sugar(d) : classe sugar, ajoute 0.20 et + sugar ;
  • add_syrup(d, flavour) : classe syrup avec le champ flavour, ajoute 0.80 et + vanilla syrup.

Écrivez leurs méthodes cost() et describe() en interrogeant d’abord la boisson interne. Le code fourni lit des lignes telles que milk,syrup vanilla,sugar (une ligne vide correspond à un café nature) et affiche coffee + milk + vanilla syrup + sugar: 3.50.

Votre code doit être placé dans Drink.R et Extras.R. main.R contient le code fourni pour les entrées et les sorties et ne peut pas être modifié.

Essayez vous-même

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

# Code d'entrée/sortie fourni : le garder tel quel
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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Compilateur R en ligne