Wprowadzenie

Wzorce projektowe to sprawdzone rozwiązania powtarzających się problemów projektowych w programowaniu. Jednym z bardziej praktycznych wzorców jest Strategia (ang. Strategy). Pozwala on na definiowanie różnych algorytmów, które można stosować zamiennie, bez konieczności modyfikowania kodu klas, które z nich korzystają.
Dzięki temu aplikacja staje się bardziej elastyczna, łatwiejsza do utrzymania i rozszerzalna. Strategia jest szczególnie przydatna w sytuacjach, gdy chcemy zmieniać sposób działania programu w zależności od okoliczności biznesowych.
Idea wzorca Strategia
Wzorzec Strategia składa się z trzech głównych elementów:
- Interfejs Strategii – definiuje wspólny kontrakt dla wszystkich algorytmów.
- Konkretnych Strategii – implementacje różnych sposobów rozwiązania problemu.
- Kontekstu – klasa, która używa strategii, ale nie zna szczegółów jej implementacji.
Dzięki temu możemy w dowolnym momencie „podmienić” strategię i zmienić sposób działania systemu bez ingerencji w jego strukturę.

Przykład z prefabrykacji żelbetowej

Prefabrykacja żelbetowa to dziedzina, w której logistyka odgrywa ogromną rolę. Elementy takie jak słupy, belki czy płyty stropowe mają duże rozmiary i masę, co powoduje, że koszty transportu są istotnym czynnikiem ekonomicznym całego przedsięwzięcia.
Koszt transportu prefabrykatu można obliczać na różne sposoby – w zależności od zastosowanego środka transportu. Inaczej wyglądają koszty przewozu TIR-em, inaczej transportu koleją, a jeszcze inaczej operacji dźwigiem na placu budowy.
Tutaj doskonale sprawdza się wzorzec Strategia.
Poniżej przykład programu w języku Java, który pokazuje zastosowanie wzorca Strategia do obliczania kosztów transportu prefabrykatu.
// Interfejs strategii
interface TransportStrategy {
double obliczKoszt(double wagaPrefabrykatu, double dystansKm);
}
// Transport TIR-em
class TransportTirem implements TransportStrategy {
@Override
public double obliczKoszt(double waga, double dystans) {
return 3.0 * dystans + 0.05 * waga;
}
}
// Transport koleją
class TransportKoleja implements TransportStrategy {
@Override
public double obliczKoszt(double waga, double dystans) {
return 2.0 * dystans + 0.02 * waga + 500; // opłata stała
}
}
// Transport dźwigiem na placu budowy
class TransportDzwigiem implements TransportStrategy {
@Override
public double obliczKoszt(double waga, double dystans) {
return 1000 + 0.1 * waga; // dystans bez znaczenia
}
}
// Kontekst
class Prefabrykat {
private String nazwa;
private double waga;
private TransportStrategy strategia;
public Prefabrykat(String nazwa, double waga) {
this.nazwa = nazwa;
this.waga = waga;
}
public void ustawStrategieTransportu(TransportStrategy strategia) {
this.strategia = strategia;
}
public void obliczKosztTransportu(double dystans) {
if (strategia == null) {
System.out.println("Nie ustawiono strategii transportu!");
return;
}
double koszt = strategia.obliczKoszt(waga, dystans);
System.out.println("Prefabrykat: " + nazwa +
", koszt transportu = " + koszt + " PLN");
}
}
// Przykładowe użycie
public class StrategiaPrefabrykacja {
public static void main(String[] args) {
Prefabrykat slup = new Prefabrykat("Słup żelbetowy", 5000);
slup.ustawStrategieTransportu(new TransportTirem());
slup.obliczKosztTransportu(120);
slup.ustawStrategieTransportu(new TransportKoleja());
slup.obliczKosztTransportu(300);
slup.ustawStrategieTransportu(new TransportDzwigiem());
slup.obliczKosztTransportu(0);
}
}
Zalety tego podejścia

Elastyczność – łatwo dodać nowy sposób transportu , wystarczy napisać nową klasę implementującą interfejs TransportStrategy.
Czytelność – logika poszczególnych sposobów transportu nie miesza się w jednej klasie
Łatwe testowanie – każdą strategię można testować niezależnie.
Możliwość dynamicznej zmiany – w trakcie działania programu możemy zmienić sposób transportu prefabrykatu w zależności od warunków
Wady

Rozdrobnienie kodu– Każda strategia to osobna klasa → przy większej liczbie strategii w projekcie może powstać kilkanaście lub kilkadziesiąt dodatkowych klas. To utrudnia nawigację i utrzymanie kodu.
Konieczność wyboru– Kontekst (np. Prefabrykat) nie wie sam, jakiej strategii powinien używać – musi to zrobić programista lub dodatkowy mechanizm decyzyjny.
– Jeśli logika wyboru jest złożona, przenosi się ona poza wzorzec, co komplikuje system.
Brak wspólnych danych między strategiami- Każda strategia implementuje interfejs, ale nie ma dostępu do wewnętrznych szczegółów innych strategii.
– Jeśli dwie strategie mają dużo wspólnego kodu, może powstać duplikacja logiki.
Dużo kodu– Jeśli mamy tylko 2–3 proste algorytmy, stosowanie Strategii może być „overengineeringiem” – czasem prosty if-else by wystarczył.
