Menu
Coddy logo textTech

데코레이터 패턴

Coddy R 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 46번째.

데코레이터는 무언가를 감싸고 동일한 인터페이스를 유지하면서 동작을 추가합니다. 함수의 경우 데코레이터는 함수를 받아 호출 전후에 추가 작업을 수행하는 새로운 함수를 반환합니다:

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(), 그리고 비용이 2이고 자신을 coffee라고 설명하는 기본 음료 new_coffee()가 들어 있습니다. Extras.R에는 세 개의 데코레이터를 작성하세요. 각 데코레이터는 자신이 감싸는 음료를 inner 필드에 보관하는 객체입니다:

  • add_milk(d): 클래스 milk이며, 0.50과 + milk를 추가합니다.
  • add_sugar(d): 클래스 sugar이며, 0.20과 + sugar를 추가합니다.
  • add_syrup(d, flavour): flavour 필드를 가진 클래스 syrup이며, 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 컴파일러