Menu
Coddy logo textTech

Padrão Observer

Parte da seção Programação Orientada a Objetos do Journey de R da Coddy. Lição 44 de 57.

O padrão observer permite que um objeto, o subject, informe a outro código quando algo acontece sem saber quem está ouvindo. O subject mantém uma lista de funções de callback, os observers, e chama cada uma delas a cada 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")

Saída:

logger saw click 
counter saw click 

Un observador puede ser un cierre con su propio estado. Este cuenta los eventos que ha visto, mientras que el sujeto no sabe nada sobre el conteo:

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

Salida:

[1] 3

Armazenar observadores sob nomes permite que eles cancelem a inscrição. observers[[name]] <<- NULL remove um, e os outros continuam recebendo eventos:

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

Saída:

ada: first 
bo: first 
bo: second 

O assunto determina quais dados um evento carrega. Um ticker de preços passa o preço antigo e o novo, e notifica somente quando o preço realmente muda:

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)

Saída:

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

Desafio

Fácil

Complete a fábrica make_newsletter() em Newsletter.R. Ela mantém os callbacks dos leitores em uma lista nomeada privada e retorna três funções:

  • subscribe(name, f) armazena o callback sob o nome (inscrever-se novamente o substitui);
  • unsubscribe(name) o remove;
  • publish(title) chama cada callback com o título, na ordem de inscrição, e retorna quantos foram notificados.

O código fornecido lê os comandos sub ada, unsub ada e post Big news, e fornece a cada leitor um callback que imprime ada got: Big news. Para cada publicação, ele imprime o título, as linhas dos callbacks e o número de notificações.

Seu código deve ficar em Newsletter.R. main.R contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

source("Newsletter.R")

# Código de entrada/saída fornecido: mantenha-o como está
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 seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Compilador de R online