Menu
CoddyTech

Mẫu Observer

Thuộc phần Lập trình hướng đối tượng trong hành trình R của Coddy. Bài học 44 trên 57.

Mẫu observer cho phép một đối tượng, gọi là subject, thông báo cho đoạn code khác khi có sự kiện xảy ra mà không cần biết ai đang lắng nghe. Subject giữ một danh sách các hàm callback, gọi là các observer, và gọi từng hàm khi có sự kiện:

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")

Đầu ra:

logger saw click 
counter saw click 

Một observer có thể là một closure với trạng thái riêng. Observer này đếm các sự kiện mà nó đã thấy, còn subject không biết gì về việc đếm:

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())

Đầu ra:

[1] 3

Lưu các observer dưới tên giúp chúng hủy đăng ký. observers[[name]] <<- NULL xóa một observer, còn những observer khác vẫn tiếp tục nhận sự kiện:

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")

Đầu ra:

ada: first 
bo: first 
bo: second 

Chủ đề quyết định sự kiện mang theo dữ liệu nào. Một bộ theo dõi giá truyền giá cũ và giá mới, đồng thời chỉ thông báo khi giá thực sự thay đổi:

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)

Đầu ra:

price 10 -> 12 
price 12 -> 9 
challenge icon

Thử thách

Dễ

Hoàn thiện hàm factory make_newsletter() trong Newsletter.R. Hàm này lưu các callback của độc giả trong một danh sách có tên riêng tư và trả về ba hàm:

  • subscribe(name, f) lưu callback dưới tên đó (nếu đăng ký lại, callback cũ sẽ được thay thế);
  • unsubscribe(name) xóa callback đó;
  • publish(title) gọi từng callback với tiêu đề, theo thứ tự đăng ký, rồi trả về số độc giả đã được thông báo.

Code được cung cấp đọc các lệnh sub ada, unsub ada và post Big news, rồi tạo cho mỗi độc giả một callback in ra ada got: Big news. Với mỗi bài đăng, code in tiêu đề, các dòng do callback in ra và số độc giả đã được thông báo.

Code của bạn nằm trong Newsletter.R. main.R chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

source("Newsletter.R")

# Đoạn code đầu vào/đầu ra được cung cấp sẵn: giữ nguyên
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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch R online