Menu
Coddy logo textTech

Observer Pattern

Part of the Object Oriented Programming section of Coddy's R journey. Lesson 44 of 57.

The observer pattern lets an object, the subject, tell other code when something happens without knowing who is listening. The subject keeps a list of callback functions, the observers, and calls each of them on every event:

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 

An observer can be a closure with its own state. This one counts the events it has seen, while the subject knows nothing about counting:

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

Storing observers under names lets them unsubscribe. observers[[name]] <<- NULL removes one, and the others keep receiving events:

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 

The subject decides what data an event carries. A price ticker passes the old and the new price, and notifies only when the price really changes:

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

Challenge

Easy

Complete the factory make_newsletter() in Newsletter.R. It keeps its readers' callbacks in a private named list and returns three functions:

  • subscribe(name, f) stores the callback under the name (subscribing again replaces it);
  • unsubscribe(name) removes it;
  • publish(title) calls every callback with the title, in subscription order, and returns how many were notified.

The supplied code reads commands sub ada, unsub ada and post Big news, and gives each reader a callback that prints ada got: Big news. For each post it prints the title, the callbacks' lines and the number notified.

Your code goes in Newsletter.R. main.R holds the supplied input/output code and cannot be edited.

Try it yourself

source("Newsletter.R")

# Supplied input/output code: keep it as it is
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 iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online R compiler