Warstwa Abstrakcji - stanowi izolację pomiędzy kodem programisty a zewnętrznymi modułami, pozwala odseparować system od jego zależności przez co czyni go podatnym na zmiany i aktualizacje. Przywiązując system do innej usługi czynimy go słabym.

Strategia

Typ: Czynnościowy

W sytuacji kiedy przebieg programu powinien zachowywać się różnie w zależności od zastanej sytuacji, możemy oczywiście zrobić tzw. "drabinkę IFów" i cieszyć się z nieczytelnego kodu o dużej złożoności cyklomatycznej. Pomijając oczywisty brak profesjonalizmu, łamiemy zasadę Open/Closed. Zatem zastanówmy się, jak stworzyć kod, który będzie czysty i w przyszłości rozszerzalny.

Przykładowa implementacja

interface Strategy
{
    public function doSomething();
}

class FirstStrategy implements Strategy
{
    public function doSomething() {
        return "I'm working under primary strategy!";
    }
}

class SecondStrategy implements Strategy
{
    public function doSomething() {
        return "I'm working under secondary strategy!";
    }
}

class Context
{
    private $strategy;

    public function __construct(Strategy $condition)
    {
        $this->strategy = $condition;
    }

    public function doSomethingUnderStrategy()
    {
        return $this->strategy->doSomething();
    }
}

Kiedy używać

Jak widać, klasa bazowa (Context) zależnie od zastanego warunku, inicjalizuje jedną z klas strategii. Klasy strategii z kolei implementują wspólny interfejs Strategy z zadeklarowaną jedną metodą. W ten sposób, kiedy mamy sytuację jak w przypadku na przykład wyboru odpowiedniego algorytmu wyszukiwania optymalnego do zastanych danych, możemy wybrać odpowiednią strategię, która go implementuje. Strategie możemy dowolnie rozszerzać dodając kolejne klasy, a ich znikoma złożoność bardzo ułatwia zrozumienie działania programu. Przećwiczmy wzorzec na przykładzie zniżek w sklepie internetowym (zniżki nie mogą się sumować, gdyby mogły, lepiej użyć wzorca dekorator).

Przykład użycia

Specyfikacja

  • Jako klient sklepu internetowego, mamy do wyboru różne zniżki
  • Po wyborze rodzaju zniżki, informacja przechodzi do oprogramowania sklepu, gdzie używana jest odpowiednia strategia zależna od wybranej zniżki
  • Aplikacja oblicza finalną cenę przy użyciu udpowiedniej strategii

Implementacja

interface Discount
{
    public function calculateDiscount($price);
}

// when there is no reason for discount
class NoDiscount implements Discount
{
    public function calculateDiscount($price)
    {
        return 0;
    }
}

// receive $10 discount if total price is greater than $19
class DiscountCoupon implements Discount
{
    public function calculateDiscount($price)
    {
        if ($price > 19) {
            return 10;
        } else {
            return 0;
        }
    }
}

// whoa! lucky you, black friday today, all prices 30% off
class BlackFriday implements Discount
{
    public function calculateDiscount($price)
    {
        return $price * 0.3;
    }
}

// buy more, gain higher discount
class ProgressDiscount implements Discount
{
    public function calculateDiscount($price)
    {
        if ($price > 50) {
            return $price * 0.20;
        } elseif ($price > 10 && $price <= 50) {
            return $price * 0.1;
        } else {
            return $price * 0.05;
        }
    }
}

class Checkout
{
    private $price;
    private $discountStrategy;

    public function __construct($price, Discount $discount)
    {
        $this->price = $price;
        $this->discountStrategy = $discount;
    }

    public function getTotalPrice()
    {
        return $this->price - $this->discountStrategy->calculateDiscount($this->price);
    }
}

class CheckoutFactory
{
    public static function create($price, $discountType)
    {
        switch ($discountType)
        {
            case 'coupon':
                $discount = new DiscountCoupon();
                break;
            case 'black_friday':
                $discount = new BlackFriday();
                break;
            case 'progress':
                $discount = new ProgressDiscount();
                break;
            default:
                throw new RuntimeException('Unknown discount type');
        }

        return new Checkout($price, $discount);
    }
}

Omówienie

Udało nam się obliczyć ostateczną cenę przy użyciu wzorca strategia. Klasa Checkout pobiera wartość zamówienia oraz rodzaj wybranego rabatu. Dzięki temu może wybrać odpowiedni algorytm obliczania zniżki i metoda getTotalPrice() jest w stanie odpowiedzieć nam na pytanie ile użytkownik musi zapłacić. Oczywiście powyższy przykład jest bardzo trywialny i w rzeczywistoście nie zdarza się aby w e-commerce występowały tak proste procesy, ale dzięki wzorcom projektowym możemy nasz sklep internetowy zaprojektować w profesjonalny sposób.

Błędy?

Znalazłeś błąd?
Chciałbyś podać lepszy przykład użycia?
A może nie zgadzasz się z implementacją przykładu?

Edytuj na Github.com Popraw lub zgłoś błąd

Pytania

Masz pytania odnośnie omawianego wzorca? Chciałbyś zapytać czy wzorzec pasuje do problemu, który aktualnie rozwiązujesz? Dobrze się składa, to jest idealne miejsce.

Błędy, sugestie ich poprawy, dyskusję na temat poprawności implementacji danego przykładu prosimy prowadzić poprzez Github Issues
Akceptuję

Ten serwis używa plików cookies. Więcej o plikach cookies.