Menu
CoddyTech

状态模式

本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 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 在线编译器