Strategy

2 minute read

Behawioralny wzorzec projektowy którego głównym celem jest wydzielenie różnych wersji danego algorytmu do osobnych klas i używanie ich zamiennie w miarę potrzeby.

Wzorzec Strategia przychodzi z pomocą gdy w naszej aplikacji za jakąś specyficzną operacje odpowiada złożona klasa wewnątrz której na podstawie zmiennych okoliczności stosujemy różne algorytmy by uzyskać oczekiwany rezultat. Wówczas powinniśmy wydzielić algorytmy do osobnych klas nazywanych strategiami. Oryginalna klasa (“kontekst”) będzie przechowywać referencje i delegować prace do obiektu wybranej strategii. Klasa trzymająca kontekst operacji nie będzie wybierać strategii samodzielnie, odpowiedzialny za to będzie kod kliencki. A więc to aplikacja wybiera jedną ze strategii zależnie od oczekiwanego rezultatu, a następnie przekazuje ją do kontekstu. Strategie implementują zgodny interfejs dzięki czemu można używać ich zamiennie. Wzorzec jest szczególnie użyteczny gdy powiązane algorytmy zakładają taki sam format danych na wejściu i wyjściu (input/output), ale nie musi się ograniczać do tej zasady.

Sposób implementacji:

  1. W klasie odpowiedzialnej za wykonanie jakieś logiki (=”kontekst”) zidentyfikuj algorytm który jest podatny na częste zmiany. Może go reprezentować duża instrukcja switch lub zbiór wyrażeń if/else, który w trakcie przetwarzania kodu wybiera jakiś wariant tego samego algorytmu do wykonania. W innych przypadkach warianty będą rozsiane po kilku klasach dziedziczących.
  2. Zadeklaruj interfejs Strategi. Powinien zawierać wszystkie operacje wspólne dla różnych wersji algorytmu. Z jego użyciem kontekst będzie w stanie wykonywać różne strategie niezależnie od ich konkretnej implementacji.
  3. Po kolei wydziel każdą wersje algorytmu do osobnej klasy. Muszą one implementować interfejs strategii.
  4. W klasie kontekstu dodaj pole które będzie przechowywać referencje do obiektu strategii. Dodaj również setter który umożliwi zmiane używanego algorytmu. Kontekst powinien wykorzystywać strategie tylko poprzez jej interfejs.
  5. Kod aplikacji wykorzystujący dany kontekst musi połączyć go z odpowiednią strategią, która zapewni oczekiwany rezultat.

Zalety i wady:

  • Open/Closed Principle. Możemy dodawać nowe strategie bez wprowadzania zmian do istniejących oraz kontekstu w którym je stosujemy
  • Composition over inheritance - pozbywamy się dziedziczenia klas na rzecz czystej kompozycji
  • Możliwość zmiany używanego algorytmu w trakcie wykonywania kodu

  • Kod kliencki musi być świadom dostępnych strategii i wybrać odpowiednią
  • Czasami nie warto wprowadzać dodatkowych klas i interfejsów związanych z tym wzorcem jeśli niepotrzebnie skomplikują program. Jeśli algorytmów jest mało i rzadko się zmieniają, być może strategia nie jest najlepszym rozwiązaniem.
  • W jezykach wspierających funkcje anonimowe dobrą alternatywą dla wzorca strategii jest implementacja algorytmów w takich funkcjach i stosowanie ich w podobny sposób jak używalibyśmy strategii. Zaletą takich funkcji będzie uproszczony kod i mniejsza ilość klas oraz interfejsów.

Przykładowy kod: GitHub

Polecam źródło Refactoring Guru: Strategy - zawiera długi opis wzorca ze świetnymi przykładami z życia, ilustracjami, diagramami klas i pseudo kodem. Znajdziesz tam również implementacje w różnych językach i opis powiązań z innymi wzorcami.