Menu
Coddy logo textTech

תבנית Decorator

חלק מהיחידה תכנות מונחה עצמים במסלול ה-R של Coddy. שיעור 46 מתוך 57.

דקורטור עוטף דבר מה ומוסיף התנהגות תוך שמירה על אותו ממשק. עבור פונקציה, הדקורטור מקבל פונקציה ומחזיר פונקציה חדשה שמבצעת עבודה נוספת מסביב לקריאה:

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

פלט:

calling double with 21 
[1] 42

דקורטור יכול לזכור תוצאות. with_cache() שומר כל תשובה בסביבה וקורא לפונקציה העטופה רק עבור ארגומנטים חדשים:

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)

פלט:

results computed: 2

אפשר לקשט גם אובייקטים. כל מקשט הוא אובייקט שמחזיק בשדה את האובייקט שעליו הוא עוטף ומשיב לאותן גנריקות, מוסיף את החלק שלו ומבקש מהאובייקט הפנימי את השאר:

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

פלט:

coffee + milk + milk = 3

אפשר לשרשר עוטפים, ולסדר שלהם יש חשיבות: החיצוני ביותר פועל ראשון בדרך פנימה ואחרון בדרך החוצה:

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

פלט:

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

אתגר

קל

משקאות מקושטים בתוספות. Drink.R מכיל את הגנריות cost() ו־describe() ואת משקה הבסיס, new_coffee(), שעולה 2 ומתאר את עצמו בתור coffee. ב־Extras.R כתבו שלוש עטיפות, שכל אחת מהן היא אובייקט שמחזיק את המשקה שאותו היא עוטפת בשדה inner:

  • add_milk(d): המחלקה milk, מוסיפה 0.50 ואת + milk;
  • add_sugar(d): המחלקה sugar, מוסיפה 0.20 ואת + sugar;
  • add_syrup(d, flavour): המחלקה syrup עם השדה flavour, מוסיפה 0.80 ואת + vanilla syrup.

כתבו את המתודות cost() ו־describe() שלהן, כך שיפנו קודם למשקה הפנימי. הקוד שסופק קורא שורות כגון milk,syrup vanilla,sugar (שורה ריקה מייצגת קפה רגיל) ומדפיס coffee + milk + vanilla syrup + sugar: 3.50.

הקוד שלכם ייכתב בקבצים Drink.R ו־Extras.R. הקובץ main.R מכיל את קוד הקלט/פלט שסופק ואסור לערוך אותו.

נסו בעצמכם

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

# קוד קלט/פלט שסופק: יש להשאיר אותו כפי שהוא
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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר R אונליין