Strategy (Stratégia) — egy viselkedési tervezési minta, amely meghatározza a felcserélhető algoritmusok családját, és mindegyiket egy külön osztályba (Strategy) helyezi. A minta lehetővé teszi az algoritmus menet közbeni kiválasztását: a klienskód egy közös Strategy interfészen keresztül működik, a konkrét implementáció pedig futásidőben kerül beillesztésre. iOS-ben a minta Protocol + stratégia osztályokon keresztül, Android-ban — Interface + implementációkon keresztül valósul meg. A Strategy — a 23 GoF minta egyike, széles körben alkalmazzák fizetések feldolgozására, validálásra, rendezésre és adatszűrésre. Bővebben — a GoF eredeti leírásában.
Lényeg
Strategy — a 23 GoF (Gang of Four) minta egyike, amelyet a «Design Patterns: Elements of Reusable Object-Oriented Software» (1994) könyv ír le. A minta megoldja az algoritmus futásidőben történő kiválasztásának problémáját. Ahelyett, hogy egy osztályt írnánk sok feltételes operátorral (if-else, switch), a Strategy azt javasolja, hogy minden algoritmust külön osztályba helyezzünk egy közös interfésszel. A kontextus (a stratégiát használó osztály) tárol egy referenciát a Strategy interfészre, és delegálja a végrehajtást a konkrét stratégiának.
A minta szerkezete három elemet foglal magában: Context (kontextus) tartalmazza a Strategy referenciáját és meghívja annak metódusát; Strategy (interfész) deklarál egy közös metódust az összes algoritmus számára; ConcreteStrategy (konkrét stratégia) implementálja az interfészt és tartalmazza a konkrét algoritmust. A kliens létrehozza a szükséges stratégiát, és átadja a kontextusnak a konstruktoron, setteren vagy metódusparaméteren keresztül. A kontextus nem tudja, melyik stratégia fut — csak az interfésszel dolgozik.
| Komponens | Szerep | Példa |
|---|---|---|
| Context | Tartalmazza a Strategy referenciáját | PaymentProcessor, Sorter |
| Strategy | Közös interfész az algoritmusok számára | Protocol PaymentStrategy |
| ConcreteStrategy | Az algoritmus konkrét implementációja | CardPayment, PayPalPayment |
Open/Closed elv — a Strategy fő előnye. A rendszer nyitott a bővítésre (új stratégia adható hozzá) és zárt a módosításra (nem kell megváltoztatni a kontextus kódját). A minta nélkül egy új algoritmus hozzáadása a meglévő osztály módosítását igényli, ami megsérti az OCP-t és növeli a regressziós hibák kockázatát. A Strategy csökkenti az osztályok méretét is: egy 200 soros switch-case osztály helyett 6 darab 20 soros osztályt kapunk.
Strategy Swift-ben a Protocol (stratégia interfész) és osztályok vagy struktúra-stratégiák segítségével implementálódik. A Swift protokollok támogatják az associated types és generic constraints szolgáltatásokat, ami rugalmasságot biztosít a stratégiák tervezésében. A kontextus általában egy ViewModel osztály vagy szolgáltatás, amely a stratégiát az init-ben vagy egy tulajdonságon keresztül fogadja. A mintát széles körben használják iOS projektekben eseménykezelésre, animációkra, adatformázásra és UI stratégiákra.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Konkrét stratégiák
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Kérés küldése a banki API-nak
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Átirányítás a PayPal SDK-ra
return PaymentResult(status: .success, transactionId: "pp_\(UUID())")
}
}
// 3. Context
class PaymentProcessor {
private var strategy: PaymentStrategy
init(strategy: PaymentStrategy) {
self.strategy = strategy
}
func setStrategy(_: PaymentStrategy) {
strategy = strategy
}
func processPayment(amount: Decimal) async throws -> PaymentResult {
return try await strategy.pay(amount: amount)
}
}
// Használat
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy SwiftUI-ban — a minta természetesen integrálódik az MVVM-mel. A ViewModel tartalmaz egy stratégia tulajdonságot, és meghívja annak metódusát a felhasználói műveletnél. A SwiftUI View @Published vagy @State segítségével kapja az adatokat — a stratégia elrejti az implementációs részleteket a View elől. Például a szövegvalidálási stratégia (emailValidator, phoneValidator) a beviteli mező típusától függően változik. A Strategy és a SwiftUI kombinációja rugalmasságot biztosít az UIKit-től való öröklés nélkül.
Strategy Kotlin-ban nyelvi szinten az Interface-t és funkcionális interfészeket (SAM) használja az egyszerűsítéshez. A Kotlin támogatja a lambdákat, ami lehetővé teszi algoritmusok függvényként való átadását külön stratégia osztály deklarálása nélkül. Android-ban a mintát a ViewModel és Use Cases osztályokban alkalmazzák az adatbetöltési, gyorsítótárazási és hibakezelési algoritmusok elkülönítésére. A Clean Architecture-t használó Android projektek a Strategy-t használják a repository különböző implementációinak befecskendezésére a flag-ektől függően (mock, real, cache).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Konkrét stratégiák
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Banki API Retrofit segítségével
return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
}
}
class PayPalPaymentStrategy(
private val email: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// PayPal SDK integration
return PaymentResult(success = true, transactionId = "pp_${UUID.randomUUID()}")
}
}
// 3. Context
class PaymentProcessor(
private val strategy: PaymentStrategy
) {
fun setStrategy(strategy: PaymentStrategy): PaymentProcessor {
return PaymentProcessor(strategy)
}
suspend fun processPayment(amount: BigDecimal): PaymentResult {
return strategy.pay(amount)
}
}
// Használat ViewModel-ben
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Eredmény feldolgozása
}
}
}
Strategy Hilt/Dagger segítségével — Android projektekben a stratégiák gyakran DI-n keresztül kerülnek befecskendezésre. A Hilt a PaymentStrategy konkrét implementációját biztosítja @Binds vagy @Provides segítségével. Ez lehetővé teszi a stratégia megváltoztatását a kontextus kódjának módosítása nélkül — elég megváltoztatni a DI modult egy másik build-hez (debug/release). Például hibakereséshez MockPaymentStrategy, éles környezetben pedig a valós banki stratégia kerül befecskendezésre. A Strategy + DI kombináció maximális rugalmasságot biztosít.
Strategy vs State — szerkezetileg a minták azonosak: mindkettő kompozíciót használ interfésszel és konkrét osztályokkal. A különbség a célban van: a Strategy független algoritmust választ, a State az objektum viselkedését kezeli az állapotától függően. A State-ben a kontextus maga változtatja meg a stratégiát az állapot változásakor, a Strategy-ben a kontextus nem irányítja a váltást — a kliens explicit módon adja meg az algoritmust. A stratégiák nem tudnak egymásról, az állapotok átléphetnek egymásba.
Strategy vs Command — a Command egyetlen műveletet ágyaz be objektumként, a Strategy felcserélhető algoritmusok halmazát ágyazza be. Command — «mit kell tenni» (egy execute hívás), Strategy — «hogyan kell tenni» (több lépésből álló algoritmus). A Command sorokhoz, késleltetett végrehajtáshoz, undo/redo-hoz használatos. A Strategy — a feladat végrehajtási módjának kiválasztásához futásidőben. A parancsok stratégiákkal paraméterezhetők, kombinálva mindkét mintát.
| Jellemző | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Cél | Felcserélhető algoritmusok | Állapotfüggő viselkedés | Kérés beágyazása | Algoritmus váz |
| Változás | Explicit a kliens által | Automatikus a kontextus által | A kliens vagy sor által | Örökléssel |
| Szint | Objektum (kompozíció) | Objektum (kompozíció) | Objektum | Osztály (öröklés) |
Strategy vs Template Method — mindkét minta algoritmusokat határoz meg, de eltérő módon. A Template Method öröklést használ: az alaposztály meghatározza az algoritmus vázát (sablon metódus), az alosztályok felülírják az egyes lépéseket. A Strategy kompozíciót használ: az algoritmus teljes egészében egy külön osztályba kerül. A Template Method egyszerűbb rögzített algoritmusszerkezetű esetekben, a Strategy — amikor az algoritmusok teljesen eltérőek és dinamikusan változhatnak.
Fizetések feldolgozása — a Strategy klasszikus példája. Az online áruház kosara terméklistát tartalmaz, a fizetési módot a felhasználó választja ki. Minden mód (kártya, PayPal, Apple Pay, Google Pay, kriptovaluta) — egy külön stratégia a pay(amount) közös aláírással. A PaymentProcessor kontextus nem tudja, hogyan történik pontosan a fizetés — meghívja a közös metódust. Új fizetési mód hozzáadása nem igényli a kosár kódjának módosítását.
Adatvalidálás — a Strategy alkalmazható ugyanazon mező különböző validálási szabályaira. Az EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implementálja a közös ValidationStrategy interfészt a validate(input) metódussal. A regisztrációs űrlap stratégiák halmazát használja az egyes mezők ellenőrzésére. A validálási stratégiák láncba (Chain of Responsibility) kapcsolhatók, vagy mind egyszerre alkalmazhatók egy ciklusban. Ez hosszú if-else ellenőrzéseket polimorf validátorok gyűjteményével váltja fel.
// Rendezési stratégia
protocol SortingStrategy {
func sort<T>(_ items: [T]) -> [T] where T: Comparable
}
struct QuickSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* quicksort */ items }
}
struct MergeSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* mergesort */ items }
}
class SortedDataSource<T> {
private var strategy: SortingStrategy
func display(_ items: [T]) { let sorted = strategy.sort(items) }
}
Hitelesítés — mobil alkalmazásokban a hitelesítési stratégiák a szolgáltatótól függően változnak. Az AuthStrategy a login(), logout(), getToken() metódusokkal implementálódik EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth számára. Az AuthManager kontextus DI-n vagy gyáron keresztül fogadja a stratégiát. Ez lehetővé teszi új hitelesítési szolgáltatók hozzáadását a bejelentkezési képernyő módosítása nélkül. A Strategy minta — alapja számos OAuth könyvtárnak és a Firebase Authentication-nek.
Gyakran Ismételt Kérdések
A Strategy akkor indokolt, ha 3+ algoritmusa van, amelyek változhatnak vagy bővíthetők. Ha 2 algoritmus van és stabilak — az egyszerű if-else kevésbé költséges. Használja a Strategy-t, ha az algoritmusokat az alkalmazás különböző részeiben használják, ha futásidőben kell algoritmusokat cserélni, vagy ha minden algoritmus saját függőségeket és teszteket igényel.
Nem, ezek különböző minták hasonló szerkezettel. Strategy — a kliens explicit módon választja ki az algoritmust, és a stratégiák függetlenek. State — az objektum maga változtatja meg a viselkedését a belső állapot változásakor, és az állapotok átléphetnek egymásba. A State-ben a kontextus kezeli az állapotváltozást, a Strategy-ben — a klienskód.
Igen, Swift-ben és Kotlin-ban a stratégia átadható closure-ként vagy lambdaként. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Ez egyszerűsíti a kódot egyszerű esetekben, de az elnevezés és dokumentáció elveszik. 1-2 algoritmushoz — a closure elegendő, 4+ esetén — jobbak a külön osztályok.
Minden stratégiát külön egységteszttel tesztelünk mock függőségekkel. A kontextust mock stratégiával teszteljük — ellenőrizzük, hogy a kontextus meghívja a stratégia metódusát és átadja a megfelelő paramétereket. Swift-ben használja az XCTest + protokollokat a mock-hoz, Kotlin-ban — MockK vagy Mockito. A fő előny: minden stratégia elkülönítve tesztelhető bonyolult konfiguráció nélkül.
Igen, a Strategy (Stratégia) — a 23 minta egyike, amelyet a «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994) könyv ír le. A viselkedési minták (Behavioral Patterns) csoportjába tartozik. Szinonimák: Policy (Politika). Az eredeti példakód Smalltalk-80-ban elérhető a GoF eredeti kiadásában.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is