Menu
Coddy logo textTech

Patron Observer

Fait partie de la section Programmation orientée objet du Journey Ruby de Coddy. Leçon 44 sur 57.

Le modèle observateur permet aux objets de s’abonner aux changements d’un autre objet, le sujet. Le sujet conserve une liste d’observateurs et avertit chacun d’eux lorsqu’un événement se produit, sans savoir ce qu’ils en font :

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

Sortie :

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

Les observateurs ont seulement besoin d’une méthode update : le duck typing. Chacun peut être une classe complètement différente, et de nouveaux observateurs peuvent être ajoutés sans modifier le sujet :

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

thermometer.subscribe(Logger2.new)   # le sujet est inchangé

Les observateurs peuvent également être des blocs. Le sujet les stocke sous forme de procs et les appelle avec call. unsubscribe supprime à nouveau un observateur :

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

Sortie :

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

Le sujet décide de ce qu’il doit envoyer. Transmettre le sujet lui-même permet à un observateur de lire ce dont il a besoin ; transmettre un nom d’événement permet à un observateur de réagir à plusieurs types de changements :

@observers.each { |o| o.update(self) }          # l'observateur lit subject.price
@observers.each { |o| o.update(:sold_out, self) }  # l'observateur vérifie l'événement
challenge icon

Défi

Facile

Complétez la classe Stock et deux observateurs. Stock.new(symbol) possède subscribe(observer) et update_price(value), qui stocke le prix et renvoie les réponses non-nil de update(stock) de chaque observateur, dans l’ordre des abonnements. PriceDisplay#update renvoie AAPL now 12.50. PriceAlert.new(limit) renvoie ALERT AAPL above 100.00 lorsque le prix dépasse la limite après être parti d’une valeur inférieure ou égale, et nil dans le cas contraire.

La première ligne d’entrée contient le symbole et la limite de l’alerte. Le code fourni abonne un affichage et une alerte, puis affiche les réponses pour chaque ligne set price.

Votre code doit se trouver dans stock.rb, price_display.rb et price_alert.rb. main.rb contient le code d’entrée/sortie fourni et ne peut pas être modifié.

Essayez vous-même

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

# Code d'entrée/sortie fourni : le garder tel quel
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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Compilateur Ruby en ligne