Menu
Coddy logo textTech

Wzorzec obserwatora

Część sekcji Programowanie obiektowe ścieżki R w Coddy. Lekcja 44 z 57.

Wzorzec obserwator pozwala obiektowi, podmiotowi, powiadamiać inny kod o wystąpieniu zdarzenia, nie wiedząc, kto nasłuchuje. Podmiot przechowuje listę funkcji zwrotnych, obserwatorów, i wywołuje każdą z nich przy każdym zdarzeniu:

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

Wynik:

logger saw click 
counter saw click 

Obserwator może być domknięciem z własnym stanem. Ten zlicza zdarzenia, które widział, podczas gdy podmiot nie wie nic o zliczaniu:

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

Wynik:

[1] 3

Przechowywanie obserwatorów pod nazwami pozwala im zrezygnować z subskrypcji. observers[[name]] <<- NULL usuwa jednego, a pozostali nadal otrzymują zdarzenia:

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

Wynik:

ada: first 
bo: first 
bo: second 

Temat decyduje o tym, jakie dane niesie zdarzenie. Ticker cenowy przekazuje starą i nową cenę oraz powiadamia tylko wtedy, gdy cena rzeczywiście się zmienia:

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)

Wynik:

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

Wyzwanie

Łatwy

Uzupełnij fabrykę make_newsletter() w pliku Newsletter.R. Przechowuje ona funkcje zwrotne swoich czytelników na prywatnej, nazwanej liście i zwraca trzy funkcje:

  • subscribe(name, f) zapisuje funkcję zwrotną pod daną nazwą (ponowna subskrypcja zastępuje poprzednią);
  • unsubscribe(name) usuwa ją;
  • publish(title) wywołuje każdą funkcję zwrotną z tytułem, w kolejności subskrypcji, i zwraca liczbę powiadomionych osób.

Dostarczony kod odczytuje polecenia sub ada, unsub ada i post Big news, a każdemu czytelnikowi przypisuje funkcję zwrotną, która wypisuje ada got: Big news. Dla każdego wpisu wypisuje tytuł, wiersze funkcji zwrotnych i liczbę powiadomionych osób.

Twój kod umieść w pliku Newsletter.R. Plik main.R zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

source("Newsletter.R")

# Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator R online