Wzorzec projektowy Strategia

Wprowadzenie


Designed by Freepik

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:

  1. Interfejs Strategii – definiuje wspólny kontrakt dla wszystkich algorytmów.
  2. Konkretnych Strategii – implementacje różnych sposobów rozwiązania problemu.
  3. 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ę.

Designed by Freepik

Przykład z prefabrykacji żelbetowej


Designed by Freepik

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


Designed by Freepik

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


Designed by Freepik

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