fbpx

Wzorce projektowe – Strategia

Wzorce projektowe – Strategia

Bardzo się cieszę, z jakim zainteresowaniem przyjęliście cykl o wzorcach. Dotarł do mnie bardzo pozytywny feedback, dziękuję. Wcześniej miałam wątpliwości czy go pisać, ponieważ w sieci jest już dużo tekstów na ten temat. Ogromnie mi miło, że mogłam przekazać Wam coś więcej, niż dotychczas było w sieci.  Dlatego po dłuższym czasie prezentuje Ci kolejny post z serii wzorców projektowych. Ostatnio pisałam o Metodzie wytwórczej. Tym razem omówię wzorzec strategia, który mi osobiście bardzo często się przydaje.

Strategia, zwana także polityką jest często wykorzystywanym wzorcem, w codziennej pracy zdarza mi się stosować ją stosunkowo w większej ilości przypadków niż inne wzorce. No, może oprócz metody fabrykującej. Jednak trzeba z nią bardzo uważać, aby nie popaść w skrajność. Tworzenie niepotrzebnych warstw abstrakcji, tam, gdzie nie są one potrzebne, a jeden if może załatwić sprawę, jest również bardzo złą praktyką.

Na czym polega strategia?

Należy ona do grypy wzorców operacyjnych, czyli takich, które zajmują się strukturą procesów, działań w szerszej perspektywie. Charakteryzuje się tym, że enkapsuluje proces. Dzieli go na mniejsze moduły, które zawierają w sobie elementy rozwiązania problemu. Strategia zarządza tymi modułami, pozwala stosować je zamiennie w zależności od kontekstu. Wiem, brzmi to dość skomplikowanie, ale jak zobaczycie na poniższym przykładzie, nie jest taka straszna.

W dużym uproszczeniu można powiedzieć, że strategia ma za zadanie w zależności od danych/kontekstu wykorzystać odpowiedni pod proces z puli algorytmów, które łączy wspólny interfejs.

Ogromną zaletą tego wzorca jest elastyczność. Jeśli pojawi się nowa sytuacja do obsłużenia, nowy algorytm. Wystarczy dodać nową klasę i nałożyć na nią odpowiedni interfejs. Jest to więc stosunkowo bardzo mała praca.

Model

Powyżej model struktury strategii. Przedstawia on klasy implementujące poszczególne strategie oraz łączący je interfejs. Poniżej przedstawiłam jedną z możliwych opcji wykorzystania strategii i wpięcia jej do kontekstu aplikacji.

Przykład Startegii

Sama idea strategi zamyka się w poniższych klasach, oczywiście może ich być więcej, w zależności ile ścieżek procesu występuje. Główna klasa oto klasa interfejsu przedstawiona poniżej. Dzięki niej klasy zawierające pod procesy są ujednolicone.

Poniższy przykład to interfejs bankomatu.  Poszczególne procesy będą wywoływane w zależności od typu operacji.

<?php

interface CacheMachineInterface
{
    /**
     * @param Transaction $transaction
     */
    public function process(Transaction $transaction);
    
    /**
     * @param string $transactionType 
     */ 
    public function isSupport(string $transactionType);
}

Poniższe klasy to już poszczególne podprocesy — strategie. W tym przypadku mamy dwa — proces odpowiadający za wpłatę do bankomatu oraz drugi za wypłatę.

<?php

class DisburseStrategy implements CacheMachineInterface
{
    /**
     * @param Transaction $transaction
     */
    public function process(Transaction $transaction)
    {
        $this->disburse($transaction);
    }
    
    
    /**
     * @param string $transactionType 
     */ 
    public function isSupport(string $transactionType)
    {
        return TransactionType::DISBURSE === $transactionType;
    }
    
    ...
}
<?php

class DepositStrategy implements CacheMachineInterface
{
    /**
     * @param Transaction $transaction
     */
    public function process(Transaction $transaction)
    {
        $this->deposit($transaction);
    }
    
    /**
     * @param string $transactionType 
     */ 
    public function isSupport(string $transactionType)
    {
        return TransactionType::DEPOSIT === $transactionType;
    }
    
    ...
}

Oto i cała idea strategii. Jednak chciałabym Ci pokazać nieco więcej. Chociażby dlatego, że uważam, że takie przedstawienie wzorca oderwanego od całości projektu niewiele daje. Dlatego poniżej chciałabym Ci pokazać, jak wygląda wykorzystanie takiej strategi w szerszym kontekście. Jest to jedno z wielu możliwości wpięcia wzorca strategii, oczywiście niejedyne.

<?php
class CacheMachineResolver
{
    /**
     * @var CacheMachineInterface[]
     */
    private $strategies;

    /**
     * @param CacheMachineInterface[] ...$strategy
     */
    public function __construct(CacheMachineInterface ...$strategy)
    {
        $this->strategies[] = $strategy;
    }

    /**
     * @param Transaction $transaction
     */
    public function resolve(Transaction $transaction)
    {
        foreach ($this->strategies as $strategy) {
            if ($strategy->isSupport($transaction->getType())) {
                $strategy->process($transaction);

                return;
            }
        }

        throw new CacheMachineResolverException(sprinf('Cannot find strategy for transaction %s', $transaction->getType()));
    }
}

Powyższa klasa zajmuje się wywołaniem odpowiedniej strategii w zależności od typu transakcji. W konstruktorze przekazujemy tablice strategii, dzięki temu uzyskujemy bardzo elastyczną strukturę. Równie dobrze możemy przekazać wszystkie strategie po kolei i w metodzie resolve wywoływać je w switchu, w tym przypadku nie będzie potrzebna metoda isSupport w poszczególnych strategiach.

Metoda resolve wywołuje na każdej strategi metodę isSupport, która sprawdza typ transakcji. Dzięki temu w klasach strategii mamy zamknięte nie tylko  samo zachowanie w poszczególnych przypadkach, ale także logikę dotyczącą sytuacji, w której ma dana strategia się odpalić. Hermetyzujemy dane i zachowanie.

Gdy wystąpi dany typ transakcji, wywołuje się odpowiednia strategia. Jeśli wystąpi nieobsługiwany typ transakcji, rzucamy exception, który możemy wyłapać w wyższej warstwie procesu.

Zastosowanie

  1. Wykorzystując charakter strategii, najlepiej sprawdzi się on w sytuacjach, gdy w zależności od kontekstu trzeba zachować się inaczej np. w przypadku przewalutowań itp.
  2. Kiedy wiele klas różni się od siebie jedynie zachowaniem i są powiązane ze sobą logiką biznesową.
  3. Jeśli klasa wyzwalająca algorytm nie musi, a nawet nie może, znać logiki za nim stojącej. Strategia hermetyzuje dane i zachowanie.
  4. W sytuacji, gdy pojawiają się w kodzie złożone instrukcje warunkowe to sygnał do zastanowienia się nad zastosowaniem strategi. Wprowadzi ona większą czytelność i elastyczność kodu.

Infografika

Oto skrót wpisu zawierający najważniejsze informacje w formie infografiki, którą możecie pobrać i sięgać do niej w razie potrzeby.

Więcej informacji możecie znaleźć u Arka, polecam. Aby być na bieżąco z nowymi wpisami, zapisz się do newslettera, używając formularza poniżej!

Zgarnij darmowy ebook i cotygodniową dawkę wiedzy

.
Magdalena Limanówka-Kuciel
magdalena@panizkomputerem.pl

Jestem programistką, która lubi mieć ręce pełne roboty. Do życia potrzebuje komputera z internetem i kubka gorącej kawy. Więcej na stronie o mnie.