Menu
Coddy logo textTech

옵저버 패턴

Coddy R 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 44번째.

옵서버 패턴을 사용하면 객체인 주체가 누가 듣고 있는지 알지 못해도 어떤 일이 발생했을 때 다른 코드에 알릴 수 있습니다. 주체는 콜백 함수의 목록인 옵서버들을 유지하고, 각 이벤트마다 모든 옵서버를 호출합니다:

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 
challenge icon

챌린지

쉬움

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 = "")
  }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 R 컴파일러