Menu
Coddy logo textTech

Patron Observateur

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

Le modèle observer permet à un objet, le subject, d’indiquer à un autre code que quelque chose s’est produit sans savoir qui écoute. Le sujet conserve une liste de fonctions de rappel, les observateurs, et appelle chacune d’elles à chaque événement :

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

Sortie :

logger saw click 
counter saw click 

Un observateur peut être une fermeture avec son propre état. Celui-ci compte les événements qu’il a vus, tandis que le sujet ne sait rien du comptage :

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

Sortie :

[1] 3

Le stockage des observateurs sous des noms leur permet de se désabonner. observers[[name]] <<- NULL en supprime un, et les autres continuent de recevoir les événements :

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

Sortie :

ada: first 
bo: first 
bo: second 

Le sujet détermine quelles données un événement transporte. Un ticker de prix transmet l’ancien et le nouveau prix, et ne notifie que lorsque le prix change réellement :

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)

Sortie :

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

Défi

Facile

Complétez la fabrique make_newsletter() dans Newsletter.R. Elle conserve les fonctions de rappel de ses lecteurs dans une liste nommée privée et renvoie trois fonctions :

  • subscribe(name, f) stocke la fonction de rappel sous le nom indiqué (un nouvel abonnement remplace le précédent) ;
  • unsubscribe(name) la supprime ;
  • publish(title) appelle chaque fonction de rappel avec le titre, dans l’ordre des abonnements, et renvoie le nombre de fonctions de rappel qui ont été appelées.

Le code fourni lit les commandes sub ada, unsub ada et post Big news, et fournit à chaque lecteur une fonction de rappel qui affiche ada got: Big news. Pour chaque publication, il affiche le titre, les lignes des fonctions de rappel et le nombre de fonctions de rappel appelées.

Votre code doit être placé dans Newsletter.R. main.R contient le code d’entrée/sortie fourni et ne peut pas être modifié.

Essayez vous-même

source("Newsletter.R")

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