Шаблон «Состояние»
Часть раздела Объектно-ориентированное программирование путешествия по 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 Задание
ЛегкоЗавершите турникет в 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 = "")
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы S3
Работа с файламиСписки с классомФункции-конструкторыМетоды print и formatВалидаторы и вспомогательные функцииПовторение — температуры4Инкапсуляция в R
Замыкания как объектыОкружения как объектыФункции доступаЗащита состоянияПовторение — парковочный счётчик7S4 Наследование
Расширение с помощью containscallNextMethodВиртуальные классыПроверка типов с помощью is()Повторение — смотритель зоопарка10Шаблоны проектирования, часть 2
Шаблон «Декоратор»Шаблон «Строитель»Шаблон «Состояние»Шаблон «Итератор»2Обобщённые функции и методы S3
Обобщённые функции и UseMethodМетоды по умолчаниюМетоды для базовых обобщённых функцийОсновы NextMethodПовторение — площади фигур5Классы S4
Определение классов S4Слоты и доступПроверка корректностиМетоды showПовторение — каталог товаров8Ссылочные классы
Определение ссылочных классовМетоды и обновление полейСемантика копированияНаследование и callSuperПовторение — очередь задач11Проект: Управление библиотекой
Книги и читателиВыдача книг3Наследование S3
Векторы классовПорядок поиска методовРасширение с помощью NextMethodПроверка с помощью inherits()Повторение — расчёт зарплаты сотрудниковПотренируйтесь самостоятельно: Онлайн-компилятор R