Kihagyás

Strategy (Stratégia)

Kategória: Viselkedési minta (Behavioral Pattern)

Lényege és célja

A Strategy minta lehetővé teszi, hogy algoritmusok (vagy üzleti logikák) egy családját hozzuk létre, azokat külön osztályokba zárjuk (kapszulázzuk), és futásidőben dinamikusan cserélhetővé tegyük őket.

A minta lényege a "Mit csinálok?" és a "Hogyan csinálom?" szétválasztása. A kliens (Context) tudja, hogy fizetni akar, de azt, hogy ez a fizetés hogyan történik (bankkártya, PayPal, utalás), a betöltött stratégia dönti el.

Miért jobb ez, mint a sok if-else?

Képzelj el egy fizetési rendszert minta nélkül:

if (paymentMethod.equals("CREDIT_CARD")) {
    // 50 sor kártyás logika
} else if (paymentMethod.equals("PAYPAL")) {
    // 50 sor paypal logika kuponokkal
}
Minden egyes új fizetési mód bevezetésével módosítani kellene ezt a központi osztályt (megsértve az Open/Closed Principle-t), és a kód egy átláthatatlan káosszá válna. A Strategy minta kompozíciót ("HAS-A") használ: a Kosár (Context) csak tart egy referenciát az interfészre, és vakon rábízza a munkát (delegál).

Mikor használjuk?

  • Amikor egy objektumon belül egy algoritmusnak több különböző változata (variációja) van, és ezek között futásidőben kell váltani (pl. fizetési módok, útvonaltervezés [séta/autó/bicikli], adat-tömörítés [ZIP/RAR]).
  • Amikor el akarunk tüntetni egy hatalmas, komplex if-else vagy switch blokkot, ami algoritmusok között választ.
  • Amikor az algoritmus (pl. a PayPal kuponozás) saját belső állapottal és függőségekkel rendelkezik, amit el akarunk rejteni a fő üzleti logika (a Kosár) elől.

Mermaid Diagram

A diagramon látható a Context (ShoppingCart) és a Strategy (PaymentStrategy) kapcsolata. A kliens kiválasztja a megfelelő konkrét stratégiát, odaadja a kosárnak, a kosár pedig egyszerűen meghívja a pay() metódust.

classDiagram
    class ShoppingCart {
        -paymentStrategy: PaymentStrategy
        +ShoppingCart(strategy: PaymentStrategy)
        +setPaymentStrategy(strategy: PaymentStrategy) void
        +checkout(amount: int) void
    }

    class PaymentStrategy {
        <<interface>>
        +pay(amount: int) void
    }

    class CreditCardPayment {
        +pay(amount: int) void
    }

    class PayPalPayment {
        -paypalCouponCode: String
        +PayPalPayment(code: String)
        +pay(amount: int) void
    }

    class Client

    Client --> ShoppingCart : "használja"

    %% A ShoppingCart kompozícióval tartalmazza a stratégiát
    ShoppingCart o--> PaymentStrategy : "delegálja a feladatot (HAS-A)"

    CreditCardPayment ..|> PaymentStrategy : "implementálja"
    PayPalPayment ..|> PaymentStrategy : "implementálja"

    Client --> CreditCardPayment : "példányosítja"
    Client --> PayPalPayment : "példányosítja"

    note for ShoppingCart "checkout(amount) {\n  paymentStrategy.pay(amount);\n}"

Forráskód

package behavioral.strategy;

interface PaymentStrategy {
    void pay(int amount);
}

class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.printf("Fizetve %s Ft bankkártyával.\n", amount);
    }
}

class PayPalPayment implements PaymentStrategy {

    private final String paypalCouponCode;

    public PayPalPayment(String paypalCouponCode) {
        this.paypalCouponCode = paypalCouponCode;
    }

    @Override
    public void pay(int amount) {
        System.out.printf("Fizetve %s Ft paypallal.\n", paypalCouponCode.equals("gemini") ? amount / 2 : amount);
    }
}

class ShoppingCart {

    private PaymentStrategy paymentStrategy;

    public ShoppingCart(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}


class Main {
    static void main() {
        var shoppingCart = new ShoppingCart(new CreditCardPayment());
        shoppingCart.checkout(3000);

        shoppingCart.setPaymentStrategy(new PayPalPayment("gemini"));
        shoppingCart.checkout(3000);
    }
}