Menu
Coddy logo textTech

Шаблон «Состояние»

Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 48 из 57.

Объект в паттерне состояние ведёт себя по-разному в зависимости от своего текущего состояния. Каждое состояние представляет собой список функций-обработчиков, и объект передаёт каждый запрос обработчикам состояния, в котором он находится:

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 
challenge icon

Задание

Легко

Завершите турникет в Turnstile.R. Список states содержит состояния locked и unlocked, и каждый обработчик возвращает list(to = ..., say = ...):

  • locked: coin разблокирует его, переводя в unlocked; push оставляет его заблокированным с результатом blocked;
  • unlocked: coin оставляет его разблокированным с результатом thanks, already open; push блокирует его с результатом welcome и засчитывает один проход.

Затем завершите make_turnstile(), которая начинает работу в заблокированном состоянии и возвращает handle(event) (ответ или ignored для неизвестного события), state() и passes(). Засчитывайте проход каждый раз, когда 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 = "")
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор R