Wzorzec Strategy
Część sekcji Programowanie obiektowe ścieżki PHP w Coddy. Lekcja 74 z 91.
Wzorzec Strategii to behawioralny wzorzec projektowy, który pozwala zdefiniować rodzinę algorytmów, zamknąć każdy z nich w osobnej klasie i sprawić, by można je było wymieniać w czasie działania programu. Obiekt korzystający ze strategii nie musi wiedzieć, który algorytm jest uruchamiany.
Wyobraź sobie koszyk zakupowy, który może stosować różne strategie rabatowe. Zamiast zakodowywać logikę rabatów na stałe za pomocą instrukcji warunkowych, definiujesz każdy rabat jako osobną klasę:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}
class NoDiscount implements DiscountStrategy {
public function calculate(float $total): float {
return $total;
}
}
class PercentageDiscount implements DiscountStrategy {
public function __construct(private int $percent) {}
public function calculate(float $total): float {
return $total - ($total * $this->percent / 100);
}
}
class FixedDiscount implements DiscountStrategy {
public function __construct(private float $amount) {}
public function calculate(float $total): float {
return max(0, $total - $this->amount);
}
}
Klasa kontekstu przyjmuje dowolną strategię i deleguje obliczenia:
<?php
class ShoppingCart {
public function __construct(private DiscountStrategy $discount) {}
public function setDiscount(DiscountStrategy $discount): void {
$this->discount = $discount;
}
public function checkout(float $total): float {
return $this->discount->calculate($total);
}
}
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100) . "\n";
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100);
Wynik:
100
80Wzorzec strategii sprawdza się, gdy istnieje wiele sposobów wykonania operacji i chcesz przełączać się między nimi bez modyfikowania klasy, która z nich korzysta. Dodanie nowego typu rabatu oznacza utworzenie nowej klasy, a nie zmianę istniejącego kodu.
Wyzwanie
ŁatwyZbudujmy kalkulator kosztów wysyłki z użyciem wzorca strategii. Różne metody wysyłki mają różne zasady naliczania opłat: standardowa wysyłka pobiera stałą opłatę, ekspresowa dolicza opłatę zależną od wagi, a wysyłka następnego dnia łączy oba podejścia. Wzorzec strategii pozwala podmieniać te obliczenia bez zmieniania kodu, który z nich korzysta.
Podzielisz kod na cztery pliki:
ShippingStrategy.php: Utwórz interfejsShippingStrategy, który określa kontrakt dla wszystkich metod wysyłki. Powinien zawierać metodęcalculate(float $weight): float, która przyjmuje wagę paczki i zwraca koszt wysyłki.ShippingMethods.php: Dołącz plik ShippingStrategy i utwórz trzy klasy implementujące ten interfejs:StandardShipping: Pobiera stałą opłatę w wysokości$5.00, niezależnie od wagiExpressShipping: Pobiera opłatę$2.00za kilogram (pomnóż wagę przez 2)OvernightShipping: Pobiera opłatę podstawową$10.00plus$3.00za kilogram
ShippingCalculator.php: Dołącz plik ShippingStrategy i utwórz klasęShippingCalculator, która pełni rolę kontekstu. Twój kalkulator powinien:- Przyjmować
ShippingStrategyw konstruktorze (użyj promocji właściwości konstruktora) - Zawierać metodę
setStrategy(ShippingStrategy $strategy): void, która pozwala zmienić metodę wysyłki w trakcie działania programu - Zawierać metodę
calculateCost(float $weight): float, która deleguje obliczenia do bieżącej strategii
- Przyjmować
main.php: Dołącz pliki ShippingMethods i ShippingCalculator. Otrzymasz dwa dane wejściowe: typ wysyłki ("standard","express"lub"overnight") oraz wagę paczki.Utwórz obiekt
ShippingCalculator, ustawiając początkowoStandardShipping. Na podstawie typu wysyłki podanego jako dane wejściowe użyjsetStrategy(), aby przełączyć się na odpowiednią metodę wysyłki. Następnie oblicz koszt dla podanej wagi i wyświetl wynik z dokładnością do dwóch miejsc po przecinku.Wynik powinien mieć postać:
Shipping cost: $[cost]
To wyzwanie pokazuje, jak wzorzec strategii zwiększa elastyczność systemu wysyłki: dodanie nowej metody wysyłki, takiej jak SameDayShipping, wymaga jedynie utworzenia nowej klasy implementującej interfejs, bez modyfikowania kalkulatora ani istniejących metod wysyłki.
Spróbuj swoich sił
<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';
// Odczytaj dane wejściowe
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));
// TODO: Utwórz ShippingCalculator z początkową strategią StandardShipping
// TODO: Na podstawie podanego typu wysyłki ("standard", "express" lub "overnight"),
// użyj setStrategy(), aby wybrać odpowiednią metodę wysyłki
// TODO: Oblicz koszt dla podanej wagi
// TODO: Wyświetl wynik sformatowany do dwóch miejsc po przecinku
// Format wyjściowy: Shipping cost: $[cost]
?>Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe $thisMetodyWłaściwościKonstruktor (__construct)Destruktor (__destruct)Powtórka — prosty kalkulator4Dziedziczenie
Podstawy dziedziczeniaSłowo kluczowe parent::Przesłanianie metodSłowo kluczowe finalKlasy abstrakcyjnePowtórzenie – hierarchia pracowników7Enkapsulacja
Public, protected, privateModyfikatory dostępu w szczegółachGettery i setteryUkrywanie informacjiPromocja parametrów konstruktora (8.0)Podsumowanie — system ewidencji studentów2Przestrzenie nazw i autoloading
Wprowadzenie do przestrzeni nazwSłowo kluczowe useStandard autoloadingu PSR-4Autoloader ComposeraPodsumowanie – uporządkowany projekt5Interfejsy i kontrakty
Wprowadzenie do interfejsówImplementowanie interfejsówImplementowanie wielu interfejsówInterfejs a klasa abstrakcyjnaPodpowiadanie typów za pomocą interfejsówPodsumowanie — kalkulator kształtów8Metody magiczne
Wprowadzenie do metod magicznych__toString i __debugInfo__get, __set, __isset, __unset__call i __callStatic__clone i klonowanie obiektów__serialize i __unserializePodsumowanie — własna kolekcja11System typów i obsługa błędów
Deklaracje typówTypy nullableTypy sumy i przecięciaKlasy wyjątkówNiestandardowa hierarchia wyjątkówTry, Catch, FinallyPodsumowanie — walidator formularzy14Projekt: Zarządzanie biblioteką
Przegląd projektuKlasy książki i użytkownika3Właściwości klas
Właściwości instancji a właściwości statyczneStałe w klasachMetody i właściwości statyczneWłaściwości prywatne i chronioneWłaściwości tylko do odczytu (PHP 8.1)Podsumowanie — menedżer kont bankowych6Polimorfizm
Ponowne omówienie nadpisywania metodPolimorfizm za pomocą interfejsówPodpowiedzi typów i typy sumyPóźne wiązanie statycznePodsumowanie — procesor płatności9Cechy
Wprowadzenie do cechKorzystanie z wielu cechRozwiązywanie konfliktów cechMetody abstrakcyjne w cechachCechy a dziedziczenie12Wzorce projektowe, część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec ObserverWzorzec StrategyPoćwicz samodzielnie: Kompilator PHP online