Wzorzec strategii pomaga uporządkować kod wtedy, gdy jedna operacja zaczyna mieć kilka wariantów, a każda kolejna reguła dokłada następny warunek. Ja patrzę na niego przede wszystkim jako na sposób na ograniczenie rozrastających się ifów, uproszczenie testów i zachowanie czytelnej architektury w aplikacji, która ma żyć dłużej niż jeden sprint. W tym tekście pokazuję, jak działa strategy pattern, kiedy naprawdę ma sens, jak wdrożyć go w JavaScript lub TypeScript oraz gdzie początkujący najczęściej przesadzają z abstrakcją.
Najważniejsze rzeczy, które warto wiedzieć o wzorcu strategii
- Wzorzec strategii wydziela zmienne algorytmy do osobnych implementacji i pozwala je podmieniać bez ruszania kodu klienta.
- Najlepiej sprawdza się tam, gdzie jedna decyzja biznesowa ma kilka wariantów, a liczba wyjątków rośnie z czasem.
- W projektach webowych często stosuje się go do wyceny, walidacji, dostawy, sortowania, formatowania danych i reguł uprawnień.
- W JavaScript i TypeScript strategią nie musi być klasa, często wystarczy funkcja albo obiekt z jedną metodą.
- Jeśli masz tylko 2-3 stabilne gałęzie i nie przewidujesz rozbudowy, zwykły warunek bywa rozsądniejszy niż pełny wzorzec.
- Największy zysk daje wtedy, gdy chcesz testować algorytmy osobno i wymieniać je bez naruszania reszty systemu.
Na czym polega wzorzec strategii
Strategy pattern to wzorzec behawioralny, w którym jeden kontekst korzysta z wielu wymiennych algorytmów, a każdy z nich robi tę samą rzecz w nieco inny sposób. Zamiast zamykać logikę w jednym dużym bloku warunkowym, wyciągam warianty do osobnych implementacji i podłączam je tam, gdzie są potrzebne. Dzięki temu kod klienta nie musi wiedzieć, jak działa algorytm, tylko który wariant został wybrany.
W praktyce składa się to z trzech elementów: kontekstu, który zleca wykonanie zadania, interfejsu strategii, który opisuje wspólne zachowanie, oraz konkretnych strategii, które zawierają różne wersje algorytmu. To podejście dobrze wspiera zasadę otwarte-zamknięte: łatwo dodaję nowy wariant, ale nie muszę modyfikować istniejącego kodu za każdym razem, gdy pojawia się kolejna reguła biznesowa.
Ja lubię ten wzorzec za to, że jest prosty koncepcyjnie, ale realnie porządkuje architekturę. Gdy już widzisz tę strukturę, naturalnie pojawia się pytanie: kiedy opłaca się ją wprowadzać, a kiedy lepiej zostać przy zwykłym warunku?
Kiedy lepiej użyć strategii niż kolejnych ifów
Nie każdy warunek trzeba od razu zamieniać w wzorzec projektowy. Jeżeli masz dwa stabilne warianty i żadnych sygnałów, że kod będzie się rozrastał, prostyif albo switch bywa czytelniejszy. Wzorzec strategii zaczyna mieć przewagę wtedy, gdy logika wyboru algorytmu przestaje być incydentalna, a staje się jednym z głównych punktów zmian w systemie.
| Sygnał w kodzie | Co to zwykle oznacza | Co robię w praktyce |
|---|---|---|
| 2-3 stabilne warianty | Zmiana jest mała i raczej nie urośnie szybko | Zostawiam prosty warunek |
| 4+ warianty lub rosnące wyjątki | Gałęzie zaczynają się dublować | Wydzielam strategię |
| Różne zasady dla różnych klientów, planów lub krajów | Algorytm zależy od kontekstu biznesowego | Rozdzielam warianty na osobne implementacje |
| Potrzeba testów jednostkowych dla samego algorytmu | Logika jest zbyt ściśle spleciona z resztą klasy | Wyciągam ją do strategii |
| Wybór algorytmu może się zmieniać w runtime | Nie wystarczy decyzja na etapie kompilacji | Podpinam strategię dynamicznie |
Właśnie tutaj najłatwiej odróżnić dobry wzorzec od sztucznej komplikacji. Jeśli decyzja ma się zmieniać, rosnąć lub być testowana osobno, strategia zwykle wygrywa. Jeśli nie, prosta gałąź pozostaje najzdrowszym wyborem. To prowadzi do pytania, jak zbudować taki układ w aplikacji bez tworzenia niepotrzebnej hierarchii.

Jak to działa w aplikacji webowej
W aplikacjach webowych najczęściej myślę o tym wzorcu jako o osobnym komponencie odpowiedzialnym za jedną decyzję biznesową: obliczenie ceny, dobranie formy dostawy, wyliczenie podatku, walidację albo formatowanie wyniku. Kontekst przyjmuje dane wejściowe, przekazuje je do wybranej strategii i zwraca efekt, nie wiedząc nic o szczegółach algorytmu.
interface ShippingStrategy {
calculate(price: number): number;
}
class StandardShipping implements ShippingStrategy {
calculate(price: number) {
return price < 200 ? 15 : 0;
}
}
class ExpressShipping implements ShippingStrategy {
calculate(price: number) {
return price < 200 ? 29 : 19;
}
}
class Checkout {
private strategy: ShippingStrategy;
constructor(strategy: ShippingStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: ShippingStrategy) {
this.strategy = strategy;
}
total(price: number) {
return price + this.strategy.calculate(price);
}
}
Ten przykład jest prosty, ale dobrze pokazuje sedno: Checkout nie zawiera logiki wyceny dostawy. Może pracować z dowolną strategią, a nowy wariant, na przykład odbiór w punkcie, nie wymaga przepisywania całej klasy. W JavaScript ten sam pomysł można zapisać jeszcze lżej, jako funkcję lub obiekt z metodą, bez budowania ciężkiej warstwy klasowej.
To właśnie dlatego w frontendzie i backendzie webowym ten wzorzec bywa wyjątkowo praktyczny. Kiedy rozumiesz przepływ zależności, łatwiej też odróżnić go od innych mechanizmów, które z zewnątrz wyglądają podobnie, ale rozwiązują inny problem.
Strategy, stan i prosty polimorfizm to nie to samo
Ten wzorzec często myli się ze stanem albo z samym polimorfizmem, bo wszystkie trzy pojęcia krążą wokół wymiennego zachowania. Różnica jest jednak ważna. W strategii wybierasz algorytm, który realizuje to samo zadanie w różny sposób. W stanie zachowanie obiektu zmienia się dlatego, że sam obiekt przechodzi między fazami życia. Polimorfizm natomiast jest cechą języka, a nie samym wzorcem.
| Mechanizm | Kto decyduje o zmianie | Na czym skupia się logika | Przykład |
|---|---|---|---|
| Wzorzec strategii | Kontekst lub warunek zewnętrzny | Wybór algorytmu | Różne sposoby liczenia rabatu |
| State pattern | Bieżący stan obiektu | Zmiana zachowania wraz z fazą życia | Zamówienie: nowe, opłacone, wysłane |
| Polimorfizm | Obiekt wywoływany przez interfejs | Wspólna metoda w różnych klasach |
calculate() w kilku implementacjach |
Zwykły switch
|
Jednorazowy warunek w kodzie | Bezpośredni wybór ścieżki | Format eksportu: PDF, CSV, JSON |
Ja zwykle upraszczam to do jednego pytania: czy wybieram sposób wykonania zadania, czy opisuję stan obiektu. Jeśli odpowiedź brzmi „sposób”, strategia jest bardzo mocnym kandydatem. Jeśli odpowiedź brzmi „stan”, lepiej patrzeć w stronę wzorca stanu. A skoro wiemy już, czego unikać na poziomie koncepcji, warto przejść do błędów, które najczęściej psują wdrożenie w praktyce.
Najczęstsze błędy przy wdrażaniu
Największy błąd, jaki widzę, to tworzenie osobnej klasy strategii dla każdej drobnej różnicy. Jeśli zmiana dotyczy jednego warunku i raczej nie będzie rosła, taki podział tylko zwiększa liczbę plików i obniża czytelność. Drugi problem to przenoszenie całej logiki wyboru do kontekstu, przez co zamiast uproszczenia dostajemy warstwę pośrednią, która nadal ma wielki blok decyzji.
- Przedwczesna abstrakcja - strategia powstaje „na zapas”, choć kod mógł zostać prosty jeszcze przez długi czas.
- Za dużo klas - każdy mikrowariant dostaje osobny plik, mimo że różnice są kosmetyczne.
- Ukryta zależność od kontekstu - strategia zaczyna znać zbyt wiele szczegółów o klasie, która ją używa.
- Brak spójnego kontraktu - każda implementacja robi coś trochę innego, więc wspólny interfejs przestaje mieć sens.
- Mylenie wzorca z architekturą całej aplikacji - strategia rozwiązuje jeden problem, nie cały projekt.
W praktyce najlepszy filtr jest dość prosty: jeśli po dodaniu nowej strategii muszę ruszyć stary kod, to znaczy, że wzorzec został wdrożony nie do końca poprawnie. Dobrze zaprojektowana strategia pozwala dopisać nowy wariant obok istniejących, bez rozbijania całego układu. To z kolei prowadzi do pytania, gdzie ten wzorzec naprawdę daje najwięcej korzyści w projektach webowych.
Gdzie ten wzorzec daje największy zwrot w projektach webowych
W projektach webowych najczęściej stosuję ten wzorzec tam, gdzie jedna operacja ma kilka sensownych wariantów, a różnice są biznesowe, nie techniczne. To są miejsca, w których kod bardzo szybko się rozjeżdża, jeśli zostanie w jednym bloku warunkowym. Poniżej są scenariusze, które naprawdę często się opłacają.
| Scenariusz | Co się zmienia | Dlaczego strategia pomaga |
|---|---|---|
| Wycena koszyka | Rabaty, prowizje, podatki, waluty | Każdą regułę można testować osobno |
| Opcje dostawy | Kurier, odbiór w punkcie, ekspres | Łatwo dodać nowy wariant bez przepisywania checkoutu |
| Walidacja formularza | Reguły zależne od kraju, typu konta lub planu | Reguły nie mieszają się w jednym warunku |
| Sortowanie i filtrowanie | Kolejność, priorytety, wielokryterialność | Nowy algorytm nie psuje starego |
| Generowanie raportów | CSV, PDF, JSON, XLSX | Format eksportu staje się wymiennym modułem |
| Uprawnienia i polityki dostępu | Różne reguły dla ról i planów | Bezpieczniej utrzymać wiele wariantów obok siebie |
Właśnie w takich miejscach wzorzec przestaje być teorią z książki, a zaczyna realnie zmniejszać koszt zmian. Ja traktuję go jak narzędzie do porządkowania zmienności: nie używam go wszędzie, ale tam, gdzie logika biznesowa ma wyraźne warianty, daje bardzo dobrą relację prostoty do elastyczności. Z tego zostaje już tylko ostatnia, praktyczna decyzja: kiedy ten wybór jest opłacalny, a kiedy lepiej zostać przy prostszym kodzie.
Kiedy ta decyzja jest naprawdę opłacalna
Wzorzec strategii ma sens wtedy, gdy koszt utrzymania jednego dużego warunku jest większy niż koszt kilku małych implementacji. To brzmi banalnie, ale właśnie na tym polega dobra decyzja architektoniczna: nie na modzie, tylko na bilansie korzyści. Ja zwykle zadaję sobie trzy pytania. Czy wariantów będzie przybywać? Czy użytkownik albo biznes wybiera je dynamicznie? Czy każdą wersję chcę testować osobno?
- Wybieram strategię, gdy mam co najmniej 3-4 warianty, a ich liczba prawdopodobnie wzrośnie.
- Wybieram strategię, gdy algorytmy różnią się na tyle, że testowanie ich w jednym miejscu staje się uciążliwe.
- Wybieram strategię, gdy chcę uniknąć kopiowania tej samej logiki w kilku klasach lub modułach.
- Rezygnuję z niej, gdy problem jest mały, stabilny i nie ma sygnałów, że będzie się rozrastał.
Jeżeli patrzysz na własny kod i widzisz coraz dłuższy blok decyzyjny, to często znak, że warto odciążyć go strategią. Jeżeli widzisz tylko dwa proste warianty i żadnej presji na rozbudowę, prostsze rozwiązanie będzie lepsze. W praktyce właśnie ta umiejętność odróżnienia jednego przypadku od drugiego robi największą różnicę w jakości architektury.