Menu
Coddy logo textTech

Beobachtermuster

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

Das Observer-Muster ermöglicht es einem Objekt, dem Subject, anderen Code darüber zu informieren, wenn etwas geschieht, ohne zu wissen, wer zuhört. Das Subject verwaltet eine Liste von Callback-Funktionen, den Observern, und ruft jede davon bei jedem Ereignis auf:

make_subject <- function() {
  observers <- list()
  list(
    subscribe = function(f) observers[[length(observers) + 1]] <<- f,
    notify = function(event) for (f in observers) f(event)
  )
}
button <- make_subject()
button$subscribe(function(e) cat("logger saw", e, "\n"))
button$subscribe(function(e) cat("counter saw", e, "\n"))
button$notify("click")

Ausgabe:

logger saw click 
counter saw click 

Ein Beobachter kann eine Closure mit einem eigenen Zustand sein. Diese zählt die Ereignisse, die sie gesehen hat, während das Subjekt nichts vom Zählen weiß:

make_subject <- function() {
  observers <- list()
  list(subscribe = function(f) observers[[length(observers) + 1]] <<- f,
       notify = function(event) for (f in observers) f(event))
}
make_counter <- function() {
  n <- 0
  list(observe = function(event) n <<- n + 1, count = function() n)
}
door <- make_subject()
c1 <- make_counter()
door$subscribe(c1$observe)
for (e in c("open", "close", "open")) door$notify(e)
print(c1$count())

Ausgabe:

[1] 3

Das Speichern von Beobachtern unter Namen ermöglicht es ihnen, sich abzumelden. observers[[name]] <<- NULL entfernt einen, während die anderen weiterhin Ereignisse empfangen:

make_subject <- function() {
  observers <- list()
  list(
    subscribe = function(name, f) observers[[name]] <<- f,
    unsubscribe = function(name) observers[[name]] <<- NULL,
    notify = function(event) for (f in observers) f(event)
  )
}
s <- make_subject()
s$subscribe("ada", function(e) cat("ada:", e, "\n"))
s$subscribe("bo", function(e) cat("bo:", e, "\n"))
s$notify("first")
s$unsubscribe("ada")
s$notify("second")

Ausgabe:

ada: first 
bo: first 
bo: second 

Das Subjekt legt fest, welche Daten ein Ereignis überträgt. Ein Preis-Ticker übergibt den alten und den neuen Preis und benachrichtigt nur, wenn sich der Preis tatsächlich ändert:

make_ticker <- function(price) {
  observers <- list()
  list(
    subscribe = function(f) observers[[length(observers) + 1]] <<- f,
    set_price = function(new) {
      if (new == price) return(invisible(NULL))
      old <- price
      price <<- new
      for (f in observers) f(old, new)
    }
  )
}
t <- make_ticker(10)
t$subscribe(function(old, new) cat("price", old, "->", new, "\n"))
for (p in c(12, 12, 9)) t$set_price(p)

Ausgabe:

price 10 -> 12 
price 12 -> 9 
challenge icon

Aufgabe

Einfach

Vervollständige die Fabrik make_newsletter() in Newsletter.R. Sie bewahrt die Rückruffunktionen ihrer Leser in einer privaten benannten Liste auf und gibt drei Funktionen zurück:

  • subscribe(name, f) speichert die Rückruffunktion unter dem Namen (ein erneutes Abonnieren ersetzt sie);
  • unsubscribe(name) entfernt sie;
  • publish(title) ruft jede Rückruffunktion mit dem Titel in der Reihenfolge der Abonnements auf und gibt zurück, wie viele benachrichtigt wurden.

Der bereitgestellte Code liest die Befehle sub ada, unsub ada und post Big news ein und gibt jedem Leser eine Rückruffunktion, die ada got: Big news ausgibt. Für jeden Beitrag gibt er den Titel, die Zeilen der Rückruffunktionen und die Anzahl der Benachrichtigten aus.

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

Probier es selbst

source("Newsletter.R")

# Gelieferter Input/Output-Code: belasse ihn wie er ist
input <- suppressWarnings(readLines(file("stdin")))
news <- make_newsletter()
reader <- function(name) {
  force(name)
  function(title) cat("  ", name, " got: ", title, "\n", sep = "")
}
for (line in input) {
  p <- strsplit(line, " ")[[1]]
  if (p[1] == "sub") news$subscribe(p[2], reader(p[2]))
  if (p[1] == "unsub") news$unsubscribe(p[2])
  if (p[1] == "post") {
    title <- paste(p[-1], collapse = " ")
    cat("post: ", title, "\n", sep = "")
    n <- news$publish(title)
    cat("  notified ", n, "\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