Wzorzec Strategii to behawioralny wzorzec projektowy, który definiuje rodzinę wymiennych algorytmów, kapsułkuje je w osobnych klasach i umożliwia ich łatwą zamianę w trakcie działania programu. Pozwala to uniknąć skomplikowanych instrukcji warunkowych (np. długich łańcuchów if-else), dzięki czemu kod staje się bardziej elastyczny, czytelny i łatwy w rozszerzaniu.
W tym artykule dla początkujących programistów omówimy wzorzec Strategii od podstaw: definicję, strukturę, zalety i wady, porównanie z innymi wzorcami oraz praktyczny przykład – aplikację do obliczania kosztów dostawy w sklepie internetowym. Zobaczysz też implementację w Javie krok po kroku z pełnym kodem źródłowym oraz wskazówki, kiedy i jak go stosować.
Czym jest wzorzec strategii? Podstawowa definicja i cel
Wzorzec Strategii należy do grupy behawioralnych wzorców projektowych opisanych przez Gang of Four w książce „Design Patterns”. Jego główna idea polega na oddzieleniu algorytmów od klasy, która ich używa. Zamiast kodować różne warianty logiki w jednej klasie (co prowadzi do „drabinki” if-else), tworzymy osobne klasy dla każdego algorytmu, implementujące wspólny interfejs. Wzorzec promuje kompozycję nad dziedziczeniem, pozwalając dynamicznie podmieniać zachowania w trakcie działania aplikacji.
Kluczowe korzyści, o których warto pamiętać:
- definiuje formalny interfejs dla rozszerzalnych rodzin algorytmów,
- bazuje na kompozycji zamiast dziedziczenia – algorytmy można wymieniać dynamicznie,
- eliminuje warunki
if-else, co poprawia czytelność i zgodność z zasadami SOLID (zwłaszcza Open-Closed Principle), - umożliwia wybór optymalnej implementacji pod kątem wydajności, pamięci czy specyficznych przypadków.
Kiedy stosować? Gdy masz wiele podobnych klas różniących się tylko zachowaniem, chcesz zmienić algorytm w runtime lub uniknąć duplikacji kodu. Przykłady: różne metody sortowania (quicksort vs. bubblesort) czy przetwarzanie płatności w e‑sklepie.
Struktura wzorca strategii – elementy i diagram UML
Wzorzec składa się z czterech głównych elementów:
- Strategia (Strategy) – abstrakcyjny interfejs lub klasa bazowa z metodą
wykonaj()(lub podobną), definiującą wspólne operacje dla wszystkich algorytmów; - Konkretna strategia (ConcreteStrategy) – klasy implementujące interfejs, zawierające specyficzną logikę (np.
StrategiaSzybka,StrategiaWolna); - Kontekst (Context) – klasa korzystająca z strategii; przechowuje referencję do aktualnej strategii i deleguje do niej zadania;
- Klient (Client) – kod używający kontekstu, który może zmieniać strategię w trakcie działania.
Wzorzec promuje kompozycję nad dziedziczeniem – kontekst „komponuje” strategię, zamiast dziedziczyć po niej.
Zalety i wady wzorca strategii
Poniżej znajdziesz zestawienie praktycznych konsekwencji stosowania wzorca:
- Elastyczność – łatwe dodawanie nowych strategii bez modyfikacji istniejącego kodu;
- Czytelność – brak rozbudowanych instrukcji warunkowych i prostsze ścieżki wykonania;
- Testowalność – każdą strategię można testować niezależnie;
- Wydajność – możliwość dobrania algorytmu optymalnego dla danych wejściowych.
Potencjalne wady, o których warto pamiętać:
- zwiększa liczbę klas – dla prostych przypadków może być overengineeringiem,
- klient musi znać różne strategie, co może komplikować kod,
- bywa nadużywany w scenariuszach, gdzie wystarczyłby prosty
switch.
Porównanie z podobnymi wzorcami
Aby wybrać właściwe podejście, porównaj strategię z innymi wzorcami o podobnym zastosowaniu:
| Wzorzec | Różnica kluczowa | Kiedy wybrać strategię zamiast…? |
|---|---|---|
| Metoda szablonowa | Dziedziczenie (statyczne, na poziomie klasy); strategia – kompozycja (dynamicznie, w czasie działania) | Gdy zachowanie ma się zmieniać w trakcie programu. |
| Polecenie (Command) | Strategia skupia się na algorytmach; polecenie koncentruje się na enkapsulacji żądań i historii | Gdy nie potrzebujesz mechanizmów undo/redo. |
| Stan (State) | Stan zmienia zachowanie obiektu na podstawie stanu; strategia – na podstawie wyboru | Gdy stan nie determinuje algorytmu. |
Dane z refactoring.guru wskazują, że strategia redukuje powtórzenia kodu dzięki definiowaniu hierarchii klas dla zachowań.
Praktyczny przykład z życia – system obliczania kosztów dostawy w e‑sklepie
Wyobraź sobie sklep internetowy, gdzie koszt dostawy zależy od metody: kurier (szybka, droga), paczkomat (tańsza) czy odbiór osobisty (darmowy). Bez wzorca kod byłby pełen if-else:
// Zły przykład – drabinka if-else
public double obliczKosztDostawy(double waga, String metoda) {
if (metoda.equals("kurier")) return waga * 10 + 15;
else if (metoda.equals("paczkomat")) return waga * 5 + 8;
else if (metoda.equals("osobisty")) return 0;
// ... nowe metody wymagają zmiany tej metody!
}
To łamie zasadę Open-Closed (otwarty na rozszerzenia, zamknięty na modyfikacje). Pora na refaktoryzację do Strategii.
Implementacja krok po kroku w Javie
- Interfejs strategia – wspólny kontrakt dla algorytmów:
public interface StrategiaDostawy {
double obliczKoszt(double waga); // Wspólna metoda
}
- Konkretne strategie – implementacje interfejsu z logiką kosztów:
public class KurierStrategia implements StrategiaDostawy {
@Override
public double obliczKoszt(double waga) {
return waga * 10 + 15; // Szybka dostawa
}
}public class PaczkomatStrategia implements StrategiaDostawy {
@Override
public double obliczKoszt(double waga) {
return waga * 5 + 8;
}
}public class OdbiorOsobistyStrategia implements StrategiaDostawy {
@Override
public double obliczKoszt(double waga) {
return 0; // Darmowy
}
}
- Kontekst – KoszykZamowienia – klasa delegująca obliczenia do strategii:
public class KoszykZamowienia {
private StrategiaDostawy strategia;public KoszykZamowienia(StrategiaDostawy strategia) {
this.strategia = strategia;
}public void ustawStrategie(StrategiaDostawy nowaStrategia) {
this.strategia = nowaStrategia; // Wymiana w runtime
}public double obliczCalkowityKoszt(double waga) {
return strategia.obliczKoszt(waga); // Delegacja
}
}
- Klient – użycie – przykład zastosowania w metodzie
main:
public class Sklep {
public static void main(String[] args) {
KoszykZamowienia koszyk = new KoszykZamowienia(new KurierStrategia());
System.out.println("Kurier: " + koszyk.obliczCalkowityKoszt(2.5)); // 40.0koszyk.ustawStrategie(new PaczkomatStrategia());
System.out.println("Paczkomat: " + koszyk.obliczCalkowityKoszt(2.5)); // 20.5koszyk.ustawStrategie(new OdbiorOsobistyStrategia());
System.out.println("Osobisty: " + koszyk.obliczCalkowityKoszt(2.5)); // 0.0
}
}
Ten przykład pokazuje, że różne scenariusze dostawy są obsługiwane bez modyfikacji klasy KoszykZamowienia. Dodanie nowej strategii (np. DronStrategia) wymaga jedynie utworzenia nowej klasy.
Inny przykład – analiza kodu źródłowego w różnych językach
Program analizujący statystyki kodu (liczba linii, klas) dla Java, Python czy C#. Każdy język ma inną logikę parsowania – strategia wydziela ją do klas z interfejsem analizujKod().
Kiedy unikać wzorca strategii?
Unikaj strategii, gdy masz zaledwie 2–3 bardzo proste warunki – wówczas wystarczy nieskomplikowany switch. Jeśli strategie różnią się minimalnie, rozważ przekazanie parametru do jednej metody zamiast mnożenia klas.
Podsumowanie kluczowych zasad wdrożenia
Poniższe praktyki pomogą wdrożyć wzorzec Strategii poprawnie i bez zbędnej złożoności:
- zawsze używaj interfejsu dla strategii,
- deleguj całość logiki do strategii,
- testuj strategie osobno.





