Шаблон Observer
Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 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 Задание
ЛегкоЗавершите фабрику make_newsletter() в Newsletter.R. Она хранит колбэки своих читателей в приватном именованном списке и возвращает три функции:
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 = "")
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы S3
Работа с файламиСписки с классомФункции-конструкторыМетоды print и formatВалидаторы и вспомогательные функцииПовторение — температуры4Инкапсуляция в R
Замыкания как объектыОкружения как объектыФункции доступаЗащита состоянияПовторение — парковочный счётчик7S4 Наследование
Расширение с помощью containscallNextMethodВиртуальные классыПроверка типов с помощью is()Повторение — смотритель зоопарка10Шаблоны проектирования, часть 2
Шаблон «Декоратор»Шаблон «Строитель»Шаблон «Состояние»Шаблон «Итератор»2Обобщённые функции и методы S3
Обобщённые функции и UseMethodМетоды по умолчаниюМетоды для базовых обобщённых функцийОсновы NextMethodПовторение — площади фигур5Классы S4
Определение классов S4Слоты и доступПроверка корректностиМетоды showПовторение — каталог товаров8Ссылочные классы
Определение ссылочных классовМетоды и обновление полейСемантика копированияНаследование и callSuperПовторение — очередь задач11Проект: Управление библиотекой
Книги и читателиВыдача книг3Наследование S3
Векторы классовПорядок поиска методовРасширение с помощью NextMethodПроверка с помощью inherits()Повторение — расчёт зарплаты сотрудниковПотренируйтесь самостоятельно: Онлайн-компилятор R