Menu
Coddy logo textTech

Beobachtermuster

Teil des Abschnitts Objektorientierte Programmierung der Ruby-Journey von Coddy. Lektion 44 von 57.

Das Observer-Muster ermöglicht es Objekten, Änderungen eines anderen Objekts, des Subjekts, zu abonnieren. Das Subjekt verwaltet eine Liste von Beobachtern und benachrichtigt jeden einzelnen, wenn etwas geschieht, ohne zu wissen, was sie damit machen:

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

Ausgabe:

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

Die Beobachter benötigen lediglich eine update-Methode: Duck-Typing. Jeder kann eine völlig andere Klasse sein, und neue Beobachter können hinzugefügt werden, ohne das Subjekt zu ändern:

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

thermometer.subscribe(Logger2.new)   # das Subjekt ist unverändert

Beobachter können ebenfalls Blöcke sein. Das Subjekt speichert sie als Procs und ruft sie mit call auf. unsubscribe entfernt einen Beobachter wieder:

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

Ausgabe:

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

Das Subjekt entscheidet, was gesendet werden soll. Wenn du das Subjekt selbst übergibst, kann ein Observer die benötigten Informationen lesen; wenn du einen Ereignisnamen übergibst, kann ein Observer auf mehrere Arten von Änderungen reagieren:

@observers.each { |o| o.update(self) }          # Beobachter liest subject.price
@observers.each { |o| o.update(:sold_out, self) }  # Beobachter prüft das Event
challenge icon

Aufgabe

Einfach

Vervollständige die Klasse Stock und zwei Beobachter. Stock.new(symbol) verfügt über subscribe(observer) und update_price(value), wobei der Preis gespeichert wird und die nicht-nil-Antworten von update(stock) für jeden Beobachter in der Reihenfolge der Anmeldung zurückgegeben werden. PriceDisplay#update gibt AAPL now 12.50 zurück. PriceAlert.new(limit) gibt ALERT AAPL above 100.00 zurück, wenn der Preis von einem Wert am oder unter dem Limit über das Limit steigt, und andernfalls nil.

Die erste Eingabezeile enthält das Symbol und das Alarmlimit. Der bereitgestellte Code abonniert eine Anzeige und einen Alarm und gibt anschließend die Antworten für jede set price-Zeile aus.

Dein Code gehört in stock.rb, price_display.rb und price_alert.rb. main.rb enthält den bereitgestellten Ein-/Ausgabecode und kann nicht bearbeitet werden.

Probier es selbst

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

# Bereitgestellter Ein-/Ausgabe-Code: belasse ihn unverändert
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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Ruby-Compiler