Strategy (Strategie) — un model comportamental de proiectare care definește o familie de algoritmi interschimbabili și plasează fiecare dintre ei într-o clasă separată (Strategy). Modelul permite alegerea algoritmului din mers: codul client funcționează printr-o interfață comună Strategy, iar implementarea concretă este introdusă în runtime. În iOS modelul se implementează prin Protocol + clase-strategii, în Android — prin Interface + implementări. Strategy — unul dintre cele 23 de modele GoF, utilizat pe scară largă pentru procesarea plăților, validare, sortare și filtrare a datelor. Mai multe — în descrierea originală GoF.
Esențial
Strategy — unul dintre cele 23 de modele GoF (Gang of Four), descris în cartea «Design Patterns: Elements of Reusable Object-Oriented Software» (1994). Modelul rezolvă problema alegerii algoritmului în runtime. În loc să scrieți o singură clasă cu mulți operatori condiționali (if-else, switch), Strategy propune separarea fiecărui algoritm într-o clasă separată cu o interfață comună. Contextul (clasa care utilizează strategia) păstrează o referință la interfața Strategy și delegă execuția strategiei concrete.
Structura modelului include trei elemente: Context (contextul) conține o referință la Strategy și apelează metoda acesteia; Strategy (interfața) declară o metodă comună pentru toți algoritmii; ConcreteStrategy (strategia concretă) implementează interfața și conține algoritmul concret. Clientul creează strategia necesară și o transmite contextului prin constructor, setter sau parametru de metodă. Contextul nu știe ce strategie se execută — lucrează doar cu interfața.
| Componentă | Rol | Exemplu |
|---|---|---|
| Context | Conține o referință la Strategy | PaymentProcessor, Sorter |
| Strategy | Interfață comună pentru algoritmi | Protocol PaymentStrategy |
| ConcreteStrategy | Implementare concretă a algoritmului | CardPayment, PayPalPayment |
Principiul Open/Closed — avantajul principal al Strategy. Sistemul este deschis pentru extindere (se poate adăuga o strategie nouă) și închis pentru modificare (nu trebuie modificat codul contextului). Fără model, adăugarea unui nou algoritm necesită modificarea clasei existente, ceea ce încalcă OCP și crește riscul erorilor de regresie. Strategy reduce de asemenea dimensiunea claselor: în loc de o clasă de 200 de rânduri cu switch-case, obțineți 6 clase a câte 20 de rânduri fiecare.
Strategy în Swift se implementează prin Protocol (interfața strategiei) și clase sau structuri-strategii. Protocoalele Swift acceptă associated types și generic constraints, ceea ce oferă flexibilitate la proiectarea strategiilor. Contextul este de obicei o clasă ViewModel sau un serviciu care primește strategia în init sau printr-o proprietate. Modelul este utilizat pe scară largă în proiectele iOS pentru procesarea evenimentelor, animații, formatarea datelor și strategii UI.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Strategii concrete
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Trimiterea cererii către API-ul bancar
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Redirecționare către PayPal SDK
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)
}
}
// Utilizare
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy în SwiftUI — modelul se integrează natural cu MVVM. ViewModel conține o proprietate strategie și apelează metoda acesteia la acțiunea utilizatorului. SwiftUI View primește date prin @Published sau @State — strategia ascunde detaliile de implementare de View. De exemplu, strategia de validare a textului (emailValidator, phoneValidator) se schimbă în funcție de tipul câmpului de intrare. Combinarea Strategy cu SwiftUI oferă flexibilitate fără moștenire de la UIKit.
Strategy în Kotlin utilizează Interface la nivel de limbaj și interfețe funcționale (SAM) pentru simplificare. Kotlin suportă lambdas, ceea ce permite transmiterea algoritmilor ca funcții fără a declara o clasă separată de strategie. În Android modelul se aplică în ViewModel și Use Cases pentru izolarea algoritmilor de încărcare a datelor, cache și gestionare a erorilor. Proiectele Android cu Clean Architecture folosesc Strategy pentru injectarea diferitelor implementări de repository în funcție de flaguri (mock, real, cache).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Strategii concrete
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// API bancar prin Retrofit
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)
}
}
// Utilizare în ViewModel
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Procesarea rezultatului
}
}
}
Strategy cu Hilt/Dagger — în proiectele Android strategiile sunt adesea injectate prin DI. Hilt furnizează implementarea concretă a PaymentStrategy prin @Binds sau @Provides. Acest lucru permite schimbarea strategiei fără modificarea codului contextului — este suficient să schimbați modulul DI pentru un alt build (debug/release). De exemplu, pentru depanare se injectează MockPaymentStrategy, pentru producție — strategia bancară reală. Combinația Strategy + DI oferă flexibilitate maximă.
Strategy vs State — structural modelele sunt identice: ambele folosesc compoziție cu interfață și clase concrete. Diferența este în scop: Strategy selectează un algoritm independent, State gestionează comportamentul obiectului în funcție de starea sa. În State contextul schimbă singur strategia la modificarea stării, în Strategy contextul nu controlează comutarea — clientul specifică explicit algoritmul. Strategiile nu știu una de alta, stările pot trece între ele.
Strategy vs Command — Command încapsulează o singură acțiune ca obiect, Strategy încapsulează un set de algoritmi interschimbabili. Command — «ce să faci» (un singur apel execute), Strategy — «cum să faci» (algoritm din mai mulți pași). Command se utilizează pentru cozi, execuție întârziată, undo/redo. Strategy — pentru alegerea modului de executare a sarcinii în runtime. Comenzile pot fi parametrizate cu strategii, combinând ambele modele.
| Caracteristică | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Scop | Algoritmi interschimbabili | Comportament în funcție de stare | Încapsularea cererii | Schetlet de algoritm |
| Schimbare | Explicit de către client | Automat de către context | De către client sau coadă | Prin moștenire |
| Nivel | Obiect (compoziție) | Obiect (compoziție) | Obiect | Clasă (moștenire) |
Strategy vs Template Method — ambele modele definesc algoritmi, dar în moduri diferite. Template Method folosește moștenirea: clasa de bază definește scheletul algoritmului (metoda șablon), subclasele suprascriu pași individuali. Strategy folosește compoziția: algoritmul este complet externalizat într-o clasă separată. Template Method este mai simplu pentru cazuri cu o structură fixă de algoritm, Strategy — când algoritmii sunt complet diferiți și se pot schimba dinamic.
Procesarea plăților — exemplul clasic de Strategy. Coșul unui magazin online conține o listă de produse, iar metoda de plată este aleasă de utilizator. Fiecare metodă (card, PayPal, Apple Pay, Google Pay, criptomonedă) — o strategie separată cu semnătura comună pay(amount). Contextul PaymentProcessor nu știe cum se efectuează exact plata — apelează metoda comună. Adăugarea unei noi metode de plată nu necesită modificarea codului coșului.
Validarea datelor — Strategy se aplică pentru diferite reguli de validare ale aceluiași câmp. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implementează interfața comună ValidationStrategy cu metoda validate(input). Formularul de înregistrare utilizează un set de strategii pentru verificarea fiecărui câmp. Strategiile de validare pot fi combinate în lanț (Chain of Responsibility) sau aplicate toate odată într-o buclă. Aceasta înlocuiește verificările lungi if-else cu o colecție de validatori polimorfici.
// Strategia de sortare
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) }
}
Autentificare — în aplicațiile mobile strategiile de autentificare se schimbă în funcție de furnizor. AuthStrategy cu metodele login(), logout(), getToken() este implementată pentru EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. Contextul AuthManager primește strategia prin DI sau fabrică. Acest lucru permite adăugarea de noi furnizori de autentificare fără modificarea ecranului de login. Modelul Strategy — baza multor biblioteci OAuth și Firebase Authentication.
Întrebări frecvente
Strategy este justificat când aveți 3+ algoritmi care se pot schimba sau extinde. Dacă aveți 2 algoritmi și sunt stabili — un simplu if-else este mai puțin costisitor. Folosiți Strategy când algoritmii sunt utilizați în diferite părți ale aplicației, când trebuie să înlocuiți algoritmi în runtime sau când fiecare algoritm necesită propriile dependențe și teste.
Nu, sunt modele diferite cu structură similară. Strategy — clientul alege explicit algoritmul, iar strategiile sunt independente. State — obiectul își schimbă singur comportamentul la modificarea stării interne, iar stările pot trece una în alta. În State contextul gestionează schimbarea stării, în Strategy — codul client.
Da, în Swift și Kotlin strategia poate fi transmisă ca closure sau lambda. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Aceasta simplifică codul pentru cazuri simple, dar se pierde denumirea și documentația. Pentru 1-2 algoritmi — closure este suficient, pentru 4+ — clase separate sunt mai bune.
Fiecare strategie este testată cu un test unitar separat cu dependențe mock. Contextul se testează cu o strategie mock — se verifică că contextul apelează metoda strategiei și transmite parametrii corecți. În Swift utilizați XCTest + protocoale pentru mock, în Kotlin — MockK sau Mockito. Avantajul principal: fiecare strategie este testată izolat fără configurare complexă.
Da, Strategy (Strategie) — unul dintre cele 23 de modele descrise în cartea «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994). Aparține grupului de modele comportamentale (Behavioral Patterns). Sinonime: Policy (Politică). Codul original de exemplu în Smalltalk-80 este disponibil în ediția originală GoF.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și