Wzorzec projektowy – Iterator

Wzorzec projektowy Iterator (ang. Iterator) pozwala na ukrycie implementacji kolekcji danych i udostępnienie interfejsu do przeszukiwania elementów w kolekcji. W przypadku stacji benzynowej, możemy użyć tego wzorca, aby udostępnić interfejs do przeszukiwania paliw dostępnych na stacji benzynowej.

Kod przed refaktoryzacją:

class GasStation
  attr_accessor :gas_types

  def initialize
    @gas_types = ["Regular", "Premium", "Diesel"]
  end
end

gas_station = GasStation.new
gas_types = gas_station.gas_types
gas_types.each { |type| puts type }

W tym kodzie, implementacja kolekcji paliw jest zawarta w klasie GasStation i jest widoczna dla innych klas. Jeśli chcielibyśmy zmienić implementację kolekcji lub dodać nowe metody do przeszukiwania elementów, musielibyśmy zmienić kod klasy GasStation.

Refaktoring

class GasStation
  def initialize
    @gas_types = ["Regular", "Premium", "Diesel"]
  end

  def iterator
    GasIterator.new(@gas_types)
  end
end

class GasIterator
  def initialize(gas_types)
    @gas_types = gas_types
    @index = 0
  end

  def has_next?
    @index < @gas_types.length
  end

  def next
    gas_type = @gas_types[@index]
    @index += 1
    gas_type
  end
end

gas_station = GasStation.new
iterator = gas_station.iterator
while iterator.has_next?
  puts iterator.next
end

W tym zrefaktoryzowanym kodzie implementacja kolekcji typów paliw jest ukryta wewnątrz klasy GasIterator i nie jest widoczna dla innych klas. Klasa GasStation ma tylko metodę, która zwraca obiekt iteratora, którego można użyć do przechodzenia przez kolekcję typów paliw. W ten sposób, jeśli będziemy musieli zmienić implementację kolekcji lub dodać nowe metody przechodzenia przez elementy, wystarczy zmodyfikować kod wewnątrz klasy GasIterator, a reszta kodu pozostanie niezmieniona.

Jednym z przypadków użycia może być aplikacja administracyjna, która musi wykonywać operacje na wszystkich dostępnych typach paliw, takie jak obliczanie całkowitej ilości sprzedaży dla określonego typu w określonym okresie lub obliczanie średniej ceny wszystkich rodzajów w czasie.

Korzyści z zastosowania iteratora w tym przypadku obejmują:

  • Zapewnienie spójnego sposobu przechodzenia przez kolekcję niezależnie od podstawowej implementacji
  • Ukrywanie szczegółów implementacji kolekcji typów paliw, co ułatwia zmianę implementacji bez wpływu na resztę kodu
  • Umożliwienie dodania do iteratora w przyszłości bardziej złożonych operacji przechodzenia, takich jak filtrowanie lub mapowanie
  • Oddzielenie problemów związanych z kolekcją i jej przechodzeniem, czyniąc kod bardziej modułowym i łatwiejszym do zrozumienia

Testy

Oto kilka testów w minitest dla refaktoryzowanej wersji. Testy te sprawdzają, czy iterator ma poprawną liczbę elementów i czy elementy są zwracane we właściwej kolejności:

require 'minitest/autorun'

class GasStationTest < Minitest::Test
  def setup
    @gas_station = GasStation.new
    @iterator = @gas_station.iterator
  end

  def test_iterator_has_next
    assert @iterator.has_next?
    @iterator.next
    assert @iterator.has_next?
    @iterator.next
    assert @iterator.has_next?
    @iterator.next
    refute @iterator.has_next?
  end

  def test_iterator_next
    assert_equal "Regular", @iterator.next
    assert_equal "Premium", @iterator.next
    assert_equal "Diesel", @iterator.next
  end
end

Leave a Reply

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