Menu
Coddy logo textTech

Pattern Observer

Fa parte della sezione Programmazione orientata agli oggetti del percorso R di Coddy. Lezione 44 di 57.

Il pattern observer permette a un oggetto, il subject, di comunicare ad altro codice quando succede qualcosa senza sapere chi sta ascoltando. Il subject mantiene un elenco di funzioni callback, gli observer, e le chiama tutte a ogni evento:

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

Output:

logger saw click 
counter saw click 

Un osservatore può essere una chiusura con un proprio stato. Questo conta gli eventi che ha visto, mentre il soggetto non sa nulla del conteggio:

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

Output:

[1] 3

Memorizzare gli osservatori con dei nomi permette loro di annullare l’iscrizione. observers[[name]] <<- NULL ne rimuove uno, mentre gli altri continuano a ricevere gli eventi:

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

Output:

ada: first 
bo: first 
bo: second 

Il soggetto determina quali dati trasporta un evento. Un ticker dei prezzi passa il prezzo precedente e quello nuovo, e invia una notifica solo quando il prezzo cambia davvero:

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)

Output:

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

Sfida

Facile

Completa la funzione factory make_newsletter() in Newsletter.R. Mantiene i callback dei lettori in una lista privata con nomi e restituisce tre funzioni:

  • subscribe(name, f) memorizza il callback con quel nome (una nuova sottoscrizione sostituisce quella precedente);
  • unsubscribe(name) lo rimuove;
  • publish(title) chiama ogni callback con il titolo, nell’ordine di sottoscrizione, e restituisce quanti lettori sono stati avvisati.

Il codice fornito legge i comandi sub ada, unsub ada e post Big news e assegna a ogni lettore un callback che stampa ada got: Big news. Per ogni post stampa il titolo, le righe dei callback e il numero di lettori avvisati.

Il tuo codice va inserito in Newsletter.R. main.R contiene il codice di input/output fornito e non può essere modificato.

Provalo tu

source("Newsletter.R")

# Codice di input/output fornito: lascialo così com'è
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 iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore R online