观察者模式
本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 44 课,共 57 课。
观察者模式让一个对象,即主题,在发生某件事时通知其他代码,而无需知道谁在监听。主题会保存一个回调函数列表,也就是观察者,并在每次事件发生时调用其中的每个函数:
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")输出:
logger saw click
counter saw click 观察者可以是一个拥有自身状态的闭包。这个观察者会统计自己收到的事件,而主题对象并不知道有人在计数:
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())输出:
[1] 3为观察者设置名称,就能让它们取消订阅。observers[[name]] <<- NULL 会移除一个观察者,其他观察者仍会继续接收事件:
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")输出:
ada: first
bo: first
bo: second 主题决定事件携带什么数据。价格行情器会传递旧价格和新价格,并且仅在价格确实发生变化时通知:
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)输出:
price 10 -> 12
price 12 -> 9 挑战
简单请在 Newsletter.R 中完成工厂函数 make_newsletter()。它会将读者的回调保存在一个私有的命名列表中,并返回三个函数:
subscribe(name, f)会将回调按名称保存(再次订阅时会替换原来的回调);unsubscribe(name)会移除该回调;publish(title)会按订阅顺序调用每个回调,并将标题传给它们,然后返回收到通知的数量。
提供的代码会读取命令 sub ada、unsub ada 和 post Big news,并为每位读者创建一个回调,该回调会打印 ada got: Big news。每次发布时,代码都会打印标题、各回调输出的行以及收到通知的数量。
请将代码写在 Newsletter.R 中。main.R 包含提供的输入/输出代码,不能编辑。
亲手试试
source("Newsletter.R")
# 提供的输入和输出代码:保持原样。
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 = "")
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: R 在线编译器