Menu
Coddy logo textTech

Decoratorパターン

CoddyのRジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 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() が含まれます。new_coffee() のコストは 2 で、自身を coffee として説明します。Extras.R には 3 つのデコレーターを記述します。それぞれ、ラップする飲み物を 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オンラインコンパイラ