Observerパターン
CoddyのRジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 44/57。
observerパターンでは、オブジェクトであるsubjectが、誰が監視しているかを知らなくても、何かが起きたときに他のコードへ知らせることができます。subjectはコールバック関数のリストであるobserverを保持し、イベントが発生するたびにそれぞれを呼び出します。
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で1つを削除し、残りのオブザーバーは引き続きイベントを受け取ります。
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 チャレンジ
簡単make_newsletter()ファクトリをNewsletter.Rに完成させてください。これは読者のコールバックを非公開の名前付きリストに保持し、次の3つの関数を返します。
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オンラインコンパイラ