Wzorzec projektowy – Obserwator

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

Leave a Reply

Your email address will not be published. Required fields are marked *