Koncepcja planu i finansów

Wzorzec strategii – przykład z życia

6 min. czytania

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:

  1. Strategia (Strategy) – abstrakcyjny interfejs lub klasa bazowa z metodą wykonaj() (lub podobną), definiującą wspólne operacje dla wszystkich algorytmów;
  2. Konkretna strategia (ConcreteStrategy) – klasy implementujące interfejs, zawierające specyficzną logikę (np. StrategiaSzybka, StrategiaWolna);
  3. Kontekst (Context) – klasa korzystająca z strategii; przechowuje referencję do aktualnej strategii i deleguje do niej zadania;
  4. 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

  1. Interfejs strategia – wspólny kontrakt dla algorytmów:
    public interface StrategiaDostawy {
    double obliczKoszt(double waga); // Wspólna metoda
    }
  2. 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
    }
    }

  3. 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
    }
    }

  4. 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.0

    koszyk.ustawStrategie(new PaczkomatStrategia());
    System.out.println("Paczkomat: " + koszyk.obliczCalkowityKoszt(2.5)); // 20.5

    koszyk.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.