Menu
Coddy logo textTech

옵서버 패턴

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

옵저버 패턴을 사용하면 객체가 다른 객체인 주체의 변경 사항을 구독할 수 있습니다. 주체는 옵저버 목록을 유지하고, 어떤 일이 발생하면 각 옵저버가 이를 어떻게 처리하는지 알지 못한 채 모두에게 알립니다:

class Thermometer
  def initialize
    @observers = []
  end

  def subscribe(observer)
    @observers << observer
  end

  def temperature=(value)
    @temperature = value
    @observers.each { |o| o.update(value) }
  end
end

class Display
  def update(value)
    puts "display: #{value} C"
  end
end

class Alarm
  def update(value)
    puts "alarm: too hot!" if value > 30
  end
end

t = Thermometer.new
t.subscribe(Display.new)
t.subscribe(Alarm.new)
t.temperature = 25
t.temperature = 35

출력:

display: 25 C
display: 35 C
alarm: too hot!

옵저버는 update 메서드만 필요합니다. 이것이 덕 타이핑입니다. 각 옵저버는 완전히 다른 클래스일 수 있으며, 주제를 변경하지 않고도 새로운 옵저버를 추가할 수 있습니다:

class Logger2
  def update(value)
    @history = (@history || []) << value
  end
end

thermometer.subscribe(Logger2.new)   # 주체는 변경되지 않음

옵저버는 블록일 수도 있습니다. 주체는 옵저버를 proc으로 저장하고 call을 사용해 호출합니다. unsubscribe는 옵저버를 다시 제거합니다:

class Button
  def initialize
    @handlers = []
  end

  def on_click(&handler)
    @handlers << handler
    handler
  end

  def unsubscribe(handler)
    @handlers.delete(handler)
  end

  def click
    @handlers.map { |h| h.call("clicked") }
  end
end

b = Button.new
b.on_click { |e| "A got #{e}" }
second = b.on_click { |e| "B got #{e}" }
p b.click
b.unsubscribe(second)
p b.click

출력:

["A got clicked", "B got clicked"]
["A got clicked"]

무엇을 보낼지는 주체가 결정합니다. 주체 자체를 전달하면 옵저버가 필요한 내용을 읽을 수 있고, 이벤트 이름을 전달하면 하나의 옵저버가 여러 종류의 변경에 반응할 수 있습니다:

@observers.each { |o| o.update(self) }          # observer가 subject.price를 읽음
@observers.each { |o| o.update(:sold_out, self) }  # observer가 이벤트를 확인함
challenge icon

챌린지

쉬움

Stock 주체와 두 옵저버를 완성하세요. Stock.new(symbol)에는 subscribe(observer)와 update_price(value)가 있으며, 후자는 가격을 저장하고 모든 옵저버에 대해 구독 순서대로 update(stock)의 nil이 아닌 응답을 반환합니다. PriceDisplay#update는 AAPL now 12.50을 반환합니다. PriceAlert.new(limit)은 가격이 해당 제한값 이하에서 제한값을 초과하도록 상승할 때 ALERT AAPL above 100.00을 반환하고, 그 외의 경우에는 nil을 반환합니다.

첫 번째 입력 줄은 심볼과 알림 제한값입니다. 제공된 코드는 디스플레이와 알림을 구독한 다음, 각 set price 줄에 대한 응답을 출력합니다.

코드는 stock.rb, price_display.rb, price_alert.rb에 작성하세요. main.rb에는 제공된 입력/출력 코드가 있으며 편집할 수 없습니다.

직접 해보기

require_relative 'stock'
require_relative 'price_display'
require_relative 'price_alert'

# 제공된 입력/출력 코드: 그대로 유지하세요
input = []
while (line = gets)
  input << line.chomp
end
symbol, limit = input[0].split
stock = Stock.new(symbol)
stock.subscribe(PriceDisplay.new)
stock.subscribe(PriceAlert.new(limit.to_f))
input.drop(1).each do |line|
  stock.update_price(line.split[1].to_f).each { |reply| puts reply }
end
quiz icon실력 점검

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

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

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