Menu
Coddy logo textTech

Pattern Observer

Fa parte della sezione Programmazione orientata agli oggetti del percorso Ruby di Coddy. Lezione 44 di 57.

Il pattern observer consente agli oggetti di iscriversi alle modifiche di un altro oggetto, il subject. Il soggetto mantiene un elenco di observer e notifica ciascuno di essi quando succede qualcosa, senza sapere cosa ne fanno:

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

Output:

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

Agli osservatori serve solo un metodo update: tipizzazione strutturale. Ognuno può essere una classe completamente diversa e si possono aggiungere nuovi osservatori senza modificare il soggetto:

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

thermometer.subscribe(Logger2.new)   # il soggetto rimane invariato

Anche gli observer possono essere blocchi. Il soggetto li memorizza come proc e li chiama con call. unsubscribe rimuove nuovamente un observer:

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

Output:

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

Il soggetto decide cosa inviare. Passare il soggetto stesso consente a un osservatore di leggere ciò di cui ha bisogno; passare il nome di un evento consente a un osservatore di reagire a diversi tipi di cambiamenti:

@observers.each { |o| o.update(self) }          # l'osservatore legge subject.price
@observers.each { |o| o.update(:sold_out, self) }  # l'osservatore controlla l'evento
challenge icon

Sfida

Facile

Completa la classe Stock e due osservatori. Stock.new(symbol) ha subscribe(observer) e update_price(value), che memorizza il prezzo e restituisce le risposte diverse da nil di update(stock) per ogni osservatore, nell’ordine di sottoscrizione. PriceDisplay#update restituisce AAPL now 12.50. PriceAlert.new(limit) restituisce ALERT AAPL above 100.00 quando il prezzo supera il limite partendo da un valore pari o inferiore, e nil altrimenti.

La prima riga di input contiene il simbolo e il limite dell’avviso. Il codice fornito sottoscrive una visualizzazione e un avviso, poi stampa le risposte per ogni riga di impostazione del prezzo.

Il tuo codice va in stock.rb, price_display.rb e price_alert.rb. main.rb contiene il codice di input/output fornito e non può essere modificato.

Provalo tu

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

# Codice di input/output fornito: lascialo così com'è
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 iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Ruby online