Wzorzec projektowy Obserwatora (ang. Observer) pozwala na rejestrowanie obiektów, które chcą być powiadamiane o zmianach stanu innych obiektów. W tym przypadku, przedstawię Ci przykład kodu, w którym centrala stacji benzynowych kontroluje ceny paliw, a stacje benzynowe muszą być powiadamiane o każdej zmianie ceny.
Przykładowy kod przed refaktoryzacją:
class CentralControl
attr_accessor :gas_price
def initialize(gas_price)
@gas_price = gas_price
end
def change_gas_price(new_price)
@gas_price = new_price
end
end
class GasStation
attr_accessor :gas_price, :central_control
def initialize(central_control)
@central_control = central_control
@gas_price = central_control.gas_price
end
def update_price
@gas_price = @central_control.gas_price
end
end
central_control = CentralControl.new(3.50)
station1 = GasStation.new(central_control)
station2 = GasStation.new(central_control)
puts "Initial price: #{station1.gas_price}"
central_control.change_gas_price(4.00)
station1.update_price
puts "New price: #{station1.gas_price}"
Ten kod tworzy klasę CentralControl
, która kontroluje cenę paliwa i ma możliwość zmiany tej ceny, oraz klasę GasStation
, która przechowuje informację o cenie paliwa i ma metodę update_price
do aktualizowania ceny paliwa. Jednakże, powiadamianie stacji benzynowej o zmianie ceny paliwa jest ręcznie obsługiwane poprzez wywołanie metody update_price
.
Warto zastosować wzorzec Obserwatora, ponieważ w przypadku, gdy centrala będzie musiała powiadamiać wiele stacji benzynowych o zmianie ceny paliwa, ręczne wywoływanie metody update_price
dla każdej stacji benzynowej stanie się trudne do zarządzania.
Refaktoring
Oto przykład kodu po refaktoryzacji z wykorzystaniem wzorca Obserwatora:
class CentralControl
attr_accessor :gas_price
attr_reader :observers
def initialize(gas_price)
@gas_price = gas_price
@observers = []
end
def change_gas_price(new_price)
@gas_price = new_price
notify_observers
end
def add_observer(observer)
@observers << observer
end
def remove_observer(observer)
@observers.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.update(self)
end
end
end
class GasStation
attr_accessor :gas_price
def initialize(central_control)
@central_control = central_control
@central_control.add_observer(self)
@gas_price = central_control.gas_price
end
def update(central_control)
@gas_price = central_control.gas_price
end
end
central_control = CentralControl.new(3.50)
station1 = GasStation.new(central_control)
station2 = GasStation.new(central_control)
puts "Initial price: #{station1.gas_price}"
central_control.change_gas_price(4.00)
puts "New price: #{station1.gas_price}"
Klasa CentralControl
teraz przechowuje listę obserwatorów (w polu observers
) i ma metody add_observer
, remove_observer
, notify_observers
do zarządzania tymi obserwatorami. Metoda change_gas_price
teraz automatycznie wywołuje metodę notify_observers
, powiadamiając każdego zarejestrowanego obserwatora o zmianie ceny paliwa. Klasa GasStation
implementuje teraz interfejs Observer
, co oznacza, że ma metodę update
, która jest wywoływana przez centralę stacji benzynowych i aktualizuje cenę paliwa na stacji benzynowej.
Dzięki temu implementacja jest bardziej elastyczna, ponieważ dodanie nowej stacji benzynowej do centrali stacji benzynowych będzie automatycznie powiadamiało ją o zmianie ceny paliwa. Podobnie, usunięcie stacji benzynowej z centrali stacji benzynowych uniemożliwi jej dalsze powiadamianie.
Testy
Poniższe testy sprawdzają, czy po zmianie ceny paliwa przez CentralControl
, cena paliwa jest również aktualizowana w obiekcie GasStation
, oraz czy możliwe jest dodanie i usunięcie obserwatora (GasStation) przez CentralControl.
require 'minitest/autorun'
class CentralControlTest < Minitest::Test
def setup
@central_control = CentralControl.new(10)
@gas_station = GasStation.new(@central_control)
end
def test_change_gas_price
@central_control.change_gas_price(20)
assert_equal 20, @central_control.gas_price
assert_equal 20, @gas_station.gas_price
end
def test_add_observer
observer = GasStation.new(@central_control)
assert_includes @central_control.observers, observer
end
def test_remove_observer
observer = GasStation.new(@central_control)
@central_control.remove_observer(observer)
refute_includes @central_control.observers, observer
end
end