Menu
Coddy logo textTech

Zustandsmuster

Teil des Abschnitts Objektorientierte Programmierung der R-Journey von Coddy. Lektion 48 von 57.

Ein Objekt im State-Muster verhält sich je nach seinem aktuellen Zustand unterschiedlich. Jeder Zustand ist eine Liste von Handler-Funktionen, und das Objekt übergibt jede Anfrage an die Handler des Zustands, in dem es sich befindet:

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
}

Ausgabe:

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

Handler nehmen ein Ereignis entgegen und geben den nächsten Zustand sowie eine Antwort zurück, sodass die Übergänge Daten statt einer Kette von ifs sind. Ein Drehkreuz wird entriegelt, wenn eine Münze eingeworfen wird, und wieder verriegelt, nachdem jemand hindurchgegangen ist:

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

Ausgabe:

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

Die Maschine selbst ist eine kleine Closure: Sie speichert den aktuellen Zustand, sucht den Handler nach und liefert für ein Ereignis, das der Zustand nicht kennt, ignored zurück:

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

Ausgabe:

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

Ein neuer Zustand ist ein neuer Eintrag in der Liste. Die Maschine ändert sich nicht, und nur die Zustände, die zum neuen Zustand führen, benötigen einen neuen Handler:

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

Ausgabe:

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

Aufgabe

Einfach

Vervollständige das Drehkreuz in Turnstile.R. Die Liste states enthält die Zustände locked und unlocked, und jeder Handler gibt list(to = ..., say = ...) zurück:

  • locked: coin entsperrt mit unlocked; push bleibt mit blocked gesperrt;
  • unlocked: coin bleibt mit thanks, already open entsperrt; push sperrt mit welcome und zählt einen Durchgang.

Vervollständige anschließend make_turnstile(), das gesperrt startet und handle(event) (die Antwort oder ignored für ein unbekanntes Ereignis), state() und passes() zurückgibt. Zähle einen Durchgang, sobald push im entsperrten Zustand verarbeitet wird. Der bereitgestellte Code liest Ereignisse ein und gibt jede Antwort mit dem neuen Zustand aus, anschließend die Anzahl der Durchgänge.

Dein Code gehört in Turnstile.R. main.R enthält den bereitgestellten Eingabe-/Ausgabecode und kann nicht bearbeitet werden.

Probier es selbst

source("Turnstile.R")

# Gelieferter Ein-/Ausgabe-Code: belasse ihn wie er ist
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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-R-Compiler