3 maja 2020

Wzorce projektowe: Strategia

Wyobraźmy sobie następujący problem. Wykonujemy aplikację dzięki której klient może zamówić sobie pewną usługę w firmie.
Na potrzeby wpisu wybierzmy np. „Seo„.

Klient może zamówić sobie usługę z gwarantowaną stawką za cały miesiąc, oraz płatną za każdy dzień w zależności od pozycji słów kluczowych w wyszukiwarkach internetowych. Ponadto, dla sklepów internetowych usługa seo będzie różniła się od tej samej usługi dla stron internetowych.

Do rozwiązania problemu, możemy wykorzystać wzorzec o nazwie „Strategia. Wymagane jest, aby program zachowywał się troszkę inaczej w zależności od sytuacji, czyli wybranego pakietu. Najgorszym rozwiązaniem będzie zastosowanie „if” np:

if ($type = 'website' && $chargeType = 'perDay') {
}

Możemy takich warunków wykonać multum, ponieważ dla każdego typu (jest ich 2) przypadają kolejne dwa rodzaje rozliczeń, a ponadto nie jesteśmy pewni czy nie powstaną kolejne w późniejszym czasie.

Poniżej przedstawiam sposób który wykorzystuje dziedziczenie oraz kompozycje.

Seo” to produkt który jest abstrakcją ponieważ możemy go zastosować do storn internetowych oraz sklepów. Więc konkretyzacja „Seo” to „WebsiteSeo” oraz „EcommerceSeo„. Strategia polega na wykonaniu osobnych klas których zadaniem jest inne obliczenie kosztu poniesionego za usługę „Seo„.

Na wstępie należy utworzyć klasę abstrakcyjną „Seo„:

abstract class Seo
{
    private $words;
    private $costStrategy;

    public function __construct(int $words, CostStrategy $costStrategy)
    {
        $this->words = $words;
        $this->costStrategy = $costStrategy;
    }

    public function words() : int
    {
        return $this->words;
    }

    public function chargeType() : string
    {
        return $this->costStrategy->chargeType();
    }

    public function cost() : int
    {
        return $this->costStrategy->cost($this);
    }
}

Z powyższej klasy abstrakcyjnej, dziedziczyć mogą poniższe dwie klasy (będą one wywoływane przez kod kliencki):

class WebsiteSeo extends Seo
{

}

class EcommerceSeo extends Seo
{

}

To czas na kilka wyjaśnień.

Powyższy przykład jest bardzo mocno uproszczony w celu prostszego wyjaśnienia idei.
Powyższe dwie klasy powinny zawierać dla nich odpowiednie metody, na potrzeby wpisu pozostawiam je puste.

Gdy tworzymy zatem obiekt jednej z powyższych dwóch klas (WebsiteSeo(), EcommerceSeo()), otrzymujemy za jego pośrednictwem dostęp do metody takich jak:

  1. words()” zwraca ilość słów które będą pozycjonowane w wyszukiwarkach internetowych.
  2. chargeType()” zwraca informacje jaka forma płatności została wybrana
  3. cost()” zwraca całościowy koszt usługi

Poniżej przedstawiam klasę abstrakcyjną „CostStrategy()

abstract class CostStrategy
{
    abstract public function chargeType() : string;
    abstract public function cost(Seo $seo) : int;
}

Oraz klasy dziedziczące:

class AnnualFixed extends CostStrategy
{
    public function chargeType() : string
    {
        return 'Stała stawka za cały rok';
    }

    public function cost(Seo $seo) : int
    {
        return 1500;
    }
}

class PerDay extends CostStrategy
{
    public function chargeType() : string
    {
        return 'Stawka zmienna za każdy dzień w zależności od pozycji w google';
    }

    public function cost(Seo $seo) : int
    {
        return ($seo->words * 15);
    }
}

Obiekty powyższych klas dziedziczących z „CostStrategy()” będą przekazywane do konstruktora klas dziedziczących z „Seo()„.

Objaśni to najlepiej poniższy kod kliencki:

$websiteSeo = new WebsiteSeo(12, PerDay());

/* ilość pozycjonowanych słów */
$websiteSeo->words(); 

/* forma płatności*/ 
$websiteSeo->chargeType();

/* koszt */
$websiteSeo->cost();

Kompozycja daje szereg możliwości przy jednoczesnym zachowaniu czytelności kodu.

Podsumowanie

Dzięki powyższemu wykorzystaniu kompozycji, utworzyliśmy kod zgodny z zasadami SOLID.

W sytuacji gdy będzie konieczne dodanie kolejnych strategii, po prostu dodamy nowe klasy, bez konieczności modyfikacji obecnych.

Wzorzec projektowy „Strategia” wykorzystujemy gdy chcemy aby nasz program zachował się inaczej w danej sytuacji. Powyższy przykład pokazuje jedną z takich sytuacji. Usługa może być klasą bazową, dziedziczącymi mogą być konkretne typy usług. Jednak każdy typ usługi może mieć inną cenę, może mieć inny zakres, czas realizacji oraz wynik końcowy. Dla tego przy pomocy kompozycji wstrzykujemy do każdej klasy dziedziczącej obiekt dzięki któremu zmieniamy strategię działania.