Menu
Coddy logo textTech

Patron État

Fait partie de la section Programmation orientée objet du Journey R de Coddy. Leçon 48 sur 57.

Un objet utilisant le modèle state se comporte différemment selon son état actuel. Chaque état est une liste de fonctions gestionnaires, et l’objet transmet chaque requête aux gestionnaires de l’état dans lequel il se trouve :

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
}

Sortie :

green : go 
yellow : slow down 
red : stop 
green : go 

Les gestionnaires prennent un événement et renvoient l’état suivant ainsi qu’une réponse, de sorte que les transitions sont des données plutôt qu’une chaîne de if. Un tourniquet se déverrouille lorsqu’une pièce est insérée et se verrouille à nouveau après le passage de quelqu’un :

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")
}

Sortie :

push -> blocked 
coin -> unlocked 
coin -> already open 
push -> welcome 

La machine elle-même est une petite fermeture : elle conserve l’état actuel, recherche le gestionnaire et renvoie ignored pour un événement que l’état ne connaît pas :

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")

Sortie :

press : light on | on 
kick : ignored | on 
press : light off | off 

Un nouvel état est une nouvelle entrée dans la liste. La machine ne change pas, et seuls les états qui mènent au nouveau ont besoin d’un nouveau gestionnaire :

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")
}

Sortie :

press : on 
overheat : fuse blown 
press : ignored
repair : repaired 
challenge icon

Défi

Facile

Complétez le tourniquet dans Turnstile.R. La liste states contient les états locked et unlocked, et chaque gestionnaire renvoie list(to = ..., say = ...) :

  • locked : coin déverrouille avec unlocked ; push reste verrouillé avec blocked ;
  • unlocked : coin reste déverrouillé avec thanks, already open ; push verrouille avec welcome et compte un passage.

Complétez ensuite make_turnstile(), qui démarre dans l'état locked et renvoie handle(event) (la réponse, ou ignored pour un événement inconnu), state() et passes(). Comptez un passage chaque fois que push est traité dans l'état unlocked. Le code fourni lit les événements et affiche chaque réponse avec le nouvel état, puis le nombre de passages.

Votre code doit se trouver dans Turnstile.R. main.R contient le code fourni d'entrée/sortie et ne peut pas être modifié.

Essayez vous-même

source("Turnstile.R")

# Code d'entrée/sortie fourni : le garder tel quel
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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Compilateur R en ligne