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] 3Storing 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
EasyComplete 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 = "")
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1S3 Basics
Working With FilesLists With a ClassConstructor FunctionsPrint and Format MethodsValidators and HelpersRecap - Temperatures4Encapsulation in R
Closures as ObjectsEnvironments as ObjectsAccessor FunctionsGuarding StateRecap - Parking Meter2S3 Generics and Methods
Generics and UseMethodDefault MethodsMethods for Base GenericsNextMethod BasicsRecap - Shape Areas8Reference Classes
Defining Reference ClassesMethods and Field UpdatesCopy SemanticsInheritance and callSuperRecap - Task Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Online R compiler