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