Wzorzec projektowy Strategii (ang. Strategy) pozwala na swobodne zmienianie algorytmów używanych przez aplikację, bez konieczności modyfikowania samego kodu. W tym przypadku, przedstawię Ci przykład kodu, w którym stacja benzynowa ma różne sposoby naliczania ceny paliw, a następnie zastosuję wzorzec Strategii do refaktoryzacji tego kodu.
Przykładowy kod przed refaktoryzacją:
class GasStation
attr_accessor :gas_price
def initialize(gas_price)
@gas_price = gas_price
end
def calculate_bill(litres)
litres * @gas_price
end
end
station = GasStation.new(3.50)
puts station.calculate_bill(10) # 35.0
Ten kod tworzy klasę GasStation
, która ma jedną metodę calculate_bill
, która mnoży cenę paliwa przez ilość litrów, aby obliczyć rachunek. Warto zastosować wzorzec Strategii, ponieważ cena paliw może się zmieniać w zależności od różnych czynników, takich jak cena surowca, opłaty rządowe itp. i chcemy mieć możliwość łatwego dodawania nowych sposobów liczenia ceny paliwa bez konieczności modyfikowania kodu GasStation
.
Refaktoring
Oto przykład kodu po refaktoryzacji z wykorzystaniem wzorca Strategii:
class GasStation
attr_accessor :gas_price, :billing_strategy
def initialize(gas_price, billing_strategy)
@gas_price = gas_price
@billing_strategy = billing_strategy
end
def calculate_bill(litres)
@billing_strategy.calculate_bill(litres, @gas_price)
end
end
class BillingStrategy
def calculate_bill(litres, gas_price)
raise 'You must implement calculate_bill in a subclass'
end
end
class SimpleBilling < BillingStrategy
def calculate_bill(litres, gas_price)
litres * gas_price
end
end
class RewardBilling < BillingStrategy
def calculate_bill(litres, gas_price)
litres * gas_price * 0.9
end
end
station = GasStation.new(3.50, SimpleBilling.new)
puts station.calculate_bill(10) # 35.0
# to switch to a different billing strategy:
station.billing_strategy = RewardBilling.new
puts station.calculate_bill(10) # 31.5
Jak widać, klasa GasStation
teraz przechowuje referencję do klasy BillingStrategy
(w polu billing_strategy
), a nie ma już własnej logiki dotyczącej liczenia rachunku. Zamiast tego, deleguje to zadanie do klasy BillingStrategy
przez wywołanie metody calculate_bill
.
Klasa BillingStrategy
jest abstrakcyjną klasą bazową, która zawiera logikę wymaganą dla implementacji klas pochodnych. W klasach pochodnych takich jak SimpleBilling
i RewardBilling
implementowane są różne strategie liczenia rachunku.
Poprzez tą implementację, kod jest bardziej elastyczny, ponieważ bez konieczności zmieniania kodu klasy GasStation
, możemy łatwo dodać nowe strategie liczenia rachunku, jedynie przez dodanie klasy pochodnej od klasy BillingStrategy
.
Testy
Na koniec zobaczmy, jak wyglądałyby testy do powyższego kodu:
require 'minitest/autorun'
class TestGasStation < Minitest::Test
def setup
@station = GasStation.new(3.50, SimpleBilling.new)
end
def test_simple_billing
assert_equal 35.0, @station.calculate_bill(10)
end
def test_reward_billing
@station.billing_strategy = RewardBilling.new
assert_equal 31.5, @station.calculate_bill(10)
end
end