post_ico4

Wzorce projektowe, cz. 2 Strategy

pobierz w .pdf
(przeznaczone do wydruku)

Druga część z serii wpisów o wzorcach projektowych. Dziś omówię wzorzec Strategii (Strategy).


Strategia jest wzorcem projektowym, który definiuje rodzinę wymiennych algorytmów i kapsułkuje je w postaci klas. Dzięki temu umożliwia wymienne stosowanie każdego z nich w trakcie działania programu.


Diagram klas wzorca Strategy

Przykładowa implementacja

<?php
interface AbstractStrategy{
    function task();
}
class ConcreteStrategy1 implements AbstractStrategy{
    public function task() {
        echo "Strategy 1";
    }
}
class ConcreteStrategy2 implements AbstractStrategy{
    public function task() {
        echo "Strategy 2";
    }
}
class Context{
    private $strategy;

    public function setStrategy(AbstractStrategy $obj) {
        $this->strategy=$obj;
    }
    public function getStrategy() {
        return $this->strategy;
    }
}
// testy
$obj = new Context();
$obj->setStrategy(new ConcreteStrategy1);
$obj->getStrategy()->task(); // wyswietla „Strategy 1”
$obj->setStrategy(new ConcreteStrategy2);
$obj->getStrategy()->task(); // wyswietla „Strategy 2”
?>

We wzorcu tym definiujemy wspólny interfejs dla wszystkich strategii. Następnie, w poszczególnych klasach implementujemy metody z konkretnymi już algorytmami. Za pomocą klasy Context możemy łatwo zmieniać używaną strategię w trakcie działania aplikacji.

Przykład z życia wzięty

Przypuśćmy, że tworzymy sklep internetowy oferujący swoje usługi w kilku państwach. Jak wiadomo prawo podatkowe znacząco różni się w poszczególnych krajach. Powstaje problem naliczenia odpowiedniego podatku dla klientów pochodzących z odmiennych państw. Jak to rozwiązać? Wybrać odpowiednią stawkę za pomocą licznych instrukcji warunkowych? Nie, do tego świetnie nadaje się wzorzec strategii.

<?php

interface Tax{
    public function count($net);
}

class TaxPL implements Tax{
    public function count($net) {
        return 0.23*$net;
    }
}

class TaxEN implements Tax{
    public function count($net) {
        return 0.15*$net;
    }
}

class TaxDE implements Tax{
    public function count($net) {
        return 0.3*$net;
    }
}

class Context{
    private $tax;

    /**
     * @return mixed
     */
    public function getTax()
    {
        return $this->tax;
    }

    /**
     * @param mixed $tax
     */
    public function setTax(Tax $tax)
    {
        $this->tax = $tax;
    }

}

// testy
$tax = new Context();
$tax->setTax(new TaxPL());
echo $tax->getTax()->count(100); // wyswietla "23"
$tax->setTax(new TAXEN());
echo $tax->getTax()->count(100); // wyswietla "15"
$tax->setTax(new TAXDE());
echo $tax->getTax()->count(100); // wyswietla "30"

?>

Za pomocą metody setTax() możemy w bardzo prosty sposób ustawić odpowiednią strategię obliczania podatku dostosowaną do państwa, z którego pochodzi nasz klient. Jeżeli sklep zdecyduje się na poszerzenie działalności o kolejny kraj, wystarczy tylko dopisać odpowiednią klasę implementującą interfejs Tax.

Zalety i wady

Zalety:

  • Eliminacja instrukcji warunkowych – kod jest bardziej przejrzysty.
  • Umożliwia wybór implementacji – algorytmy mogą rozwiązywać ten sam problem, lecz różnić się uzyskiwanymi korzyściami.
  • Łatwość dołączania kolejnych strategii.
  • Łatwiejsze testowanie programu – można debugować każdą strategię z osobna.

Wady:

  • Dodatkowy koszt komunikacji między klientem, a strategią (wywołania metod, przekazywanie danych).
  • „Rozmycie” kodu na kilka klas.

Zastosowanie

Wszędzie tam, gdzie istnieje potrzeba rozwiązania danego problemu na kilka różnych sposobów.

Oficjalna wirtualna maszyna Javy HotSpot wykorzystuje wzorzec strategii w wewnętrznej implementacji mechanizmu odśmiecania pamięci, oferując do wyboru kilka algorytmów różniących się właściwościami. Sam programista wybiera strategię odśmiecania najlepiej dopasowaną do profilu jego aplikacji.

Powiązane tematy

Co sądzisz o wpisie?
BeżnadziejnySłabyŚredniDobryBardzo dobry (2 głosów, średnia ocen: 4,50 z 5)
Loading...
  • dantekir

    Dzięki :) czekam na więcej :)

  • RD

    Nie wiem, czy switch tutaj jest najtrafniejszym przykładem, bo trochę deprecjonuje to, co strategia zakłada, czyli właśnie brak instrukcji warunkowych ;).

    Myślę, że efektywniejszym na szerszą skalę rozwiązaniem jest generowanie nazwy klasy wg konkretnego schematu (tutaj: Tax*), a później jedynie wykrywanie, czy utworzona nazwa klasy ‚Tax’.$country istnieje. Wtedy rozszerzanie faktycznie staje się proste i strategia ma sens :).

  • @RD masz rację. Starałem się by mój przykład pod względem skomplikowania był jak najprostszy dlatego użyłem tylko switch :)

  • maczos5

    Fajny opis jednak przydałoby się więcej przykładów z życia wziętych:)

  • DerekK

    Chyba wkradł się błąd, grot strzałki agregacji na diagramie powinien być skierowany ku Kontekstowi.

  • Yorano

    Hej, czy moze ktos mi wytlumaczyc po co jest AbstractStrategy w parametrach?? W przykladzie „Z zycia wziety” nie ma takiego sposobu definicji funkcji.

    public function setStrategy(AbstractStrategy $obj) {

    • W kodzie „z życia wzięty” po prostu nieco inaczej zaimplementowałem metodę. Zamiast przekazywać obiekt podaje kod kraju i tworzę odpowiedni obiekt za pomocą konstrukcji switch, case. Równie dobrze mógłbym przekazywać obiekt klasy jak w wzorcowym przykładzie :)

      • Mateusz

        Gdybyś przekazał obiekt klasy jak w wzorcowym przykładzie to switch byłby w ogóle nie potrzebny:) Myślę, że warto by dodać tak wersję.

        • Poprawione. Jakbym dzisiaj pisał ten wpis pewnie bym od razu tak stworzył :)