Stateパターン
CoddyのRジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 48/57。
stateパターンのオブジェクトは、現在の状態に応じて異なる動作をします。各状態はハンドラー関数のリストであり、オブジェクトはすべてのリクエストを、自分が属している状態のハンドラーに渡します:
states <- list(
green = list(label = "go", next_state = "yellow"),
yellow = list(label = "slow down", next_state = "red"),
red = list(label = "stop", next_state = "green")
)
current <- "green"
for (i in 1:4) {
cat(current, ":", states[[current]]$label, "\n")
current <- states[[current]]$next_state
}出力:
green : go
yellow : slow down
red : stop
green : go ハンドラーはイベントを受け取り、次の状態と応答を返すため、遷移はifの連鎖ではなくデータになります。ターンスタイルはコインが投入されるとロックを解除し、誰かが通過した後に再びロックします。
states <- list(
locked = list(
coin = function() list(to = "unlocked", say = "unlocked"),
push = function() list(to = "locked", say = "blocked")
),
unlocked = list(
coin = function() list(to = "unlocked", say = "already open"),
push = function() list(to = "locked", say = "welcome")
)
)
current <- "locked"
for (event in c("push", "coin", "coin", "push")) {
r <- states[[current]][[event]]()
current <- r$to
cat(event, "->", r$say, "\n")
}出力:
push -> blocked
coin -> unlocked
coin -> already open
push -> welcome マシン自体は小さなクロージャです。現在の状態を保持し、ハンドラーを検索し、その状態が認識しないイベントには ignored と応答します。
make_machine <- function(states, start) {
current <- start
list(
handle = function(event) {
h <- states[[current]][[event]]
if (is.null(h)) return("ignored")
r <- h()
current <<- r$to
r$say
},
state = function() current
)
}
states <- list(off = list(press = function() list(to = "on", say = "light on")),
on = list(press = function() list(to = "off", say = "light off")))
m <- make_machine(states, "off")
for (e in c("press", "kick", "press")) cat(e, ":", m$handle(e), "|", m$state(), "\n")出力:
press : light on | on
kick : ignored | on
press : light off | off 新しい状態は、リスト内の新しいエントリです。マシンは変わらず、新しい状態へつながる状態だけが新しいハンドラーを必要とします:
states <- list(
off = list(press = function() list(to = "on", say = "on")),
on = list(press = function() list(to = "off", say = "off"),
overheat = function() list(to = "broken", say = "fuse blown")),
broken = list(repair = function() list(to = "off", say = "repaired"))
)
current <- "off"
for (e in c("press", "overheat", "press", "repair")) {
h <- states[[current]][[e]]
if (is.null(h)) { cat(e, ": ignored\n"); next }
r <- h()
current <- r$to
cat(e, ":", r$say, "\n")
}出力:
press : on
overheat : fuse blown
press : ignored
repair : repaired チャレンジ
簡単Turnstile.R でターンスタイルを完成させてください。リスト states には locked と unlocked の状態があり、各ハンドラーは list(to = ..., say = ...) を返します。
- locked:
coinはunlockedでロックを解除し、pushはblockedとともにロックされたままにします。 - unlocked:
coinはthanks, already openとともにロック解除されたままにし、pushはwelcomeとともにロックし、通過を 1 回カウントします。
次に、ロックされた状態で開始し、handle(event)(返信、未知のイベントの場合は ignored)、state()、および passes() を返す make_turnstile() を完成させてください。ロック解除された状態で push が処理されるたびに、通過をカウントします。用意されているコードはイベントを読み取り、新しい状態とともに各返信を出力し、その後に通過回数を出力します。
コードは Turnstile.R に記述してください。main.R には用意されている入出力コードが含まれており、編集できません。
自分で試してみよう
source("Turnstile.R")
# 提供された入出力コード: そのままにしておく
input <- suppressWarnings(readLines(file("stdin")))
t <- make_turnstile()
for (event in input) cat(event, ": ", t$handle(event), " [", t$state(), "]\n", sep = "")
cat("passes: ", t$passes(), "\n", sep = "")
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rオンラインコンパイラ