Strategy (Strategie) — behaviorální návrhový vzor, který definuje rodinu zaměnitelných algoritmů a umísťuje každý z nich do samostatné třídy (Strategy). Vzor umožňuje vybrat algoritmus za běhu: klientský kód pracuje přes společný rozhraní Strategy a konkrétní implementace se dosazuje v runtime. V iOS se vzor implementuje přes Protocol + třídy strategie, v Android — přes Interface + implementace. Strategy — jeden z 23 vzorů GoF, široce používaný pro zpracování plateb, validaci, třídění a filtrování dat. Více — v originálním popisu GoF.
Hlavní body
Strategy — jeden z 23 vzorů GoF (Gang of Four), popsaný v knize «Design Patterns: Elements of Reusable Object-Oriented Software» (1994). Vzor řeší problém výběru algoritmu za běhu. Místo psaní jedné třídy s mnoha podmíněnými operátory (if-else, switch) Strategy navrhuje oddělit každý algoritmus do samostatné třídy se společným rozhraním. Kontext (třída používající strategii) uchovává odkaz na rozhraní Strategy a deleguje provedení na konkrétní strategii.
Struktura vzoru zahrnuje tři prvky: Context (kontext) obsahuje odkaz na Strategy a volá její metodu; Strategy (rozhraní) deklaruje společnou metodu pro všechny algoritmy; ConcreteStrategy (konkrétní strategie) implementuje rozhraní a obsahuje konkrétní algoritmus. Klient vytvoří požadovanou strategii a předá ji kontextu přes konstruktor, setter nebo parametr metody. Kontext neví, která strategie se provádí — pracuje pouze s rozhraním.
| Komponenta | Role | Příklad |
|---|---|---|
| Context | Obsahuje odkaz na Strategy | PaymentProcessor, Sorter |
| Strategy | Společné rozhraní pro algoritmy | Protocol PaymentStrategy |
| ConcreteStrategy | Konkrétní implementace algoritmu | CardPayment, PayPalPayment |
Princip Open/Closed — hlavní výhoda Strategy. Systém je otevřený pro rozšíření (lze přidat novou strategii) a uzavřený pro změnu (není třeba měnit kód kontextu). Bez vzoru vyžaduje přidání nového algoritmu změnu existující třídy, což porušuje OCP a zvyšuje riziko regresních chyb. Strategy také zmenšuje velikost tříd: místo 200řádkové třídy se switch-case získáte 6 tříd po 20 řádcích.
Strategy ve Swift se implementuje přes Protocol (rozhraní strategie) a třídy nebo struktury strategie. Swift protokoly podporují associated types a generic constraints, což poskytuje flexibilitu při navrhování strategií. Kontext je obvykle třída ViewModel nebo služba, která přijímá strategii v init nebo přes vlastnost. Vzor je široce používán v iOS projektech pro zpracování událostí, animace, formátování dat a UI strategie.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Konkrétní strategie
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Odeslání požadavku na bankovní API
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Přesměrování na 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)
}
}
// Použití
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy ve SwiftUI — vzor se přirozeně integruje s MVVM. ViewModel obsahuje vlastnost strategie a volá její metodu při akci uživatele. SwiftUI View přijímá data přes @Published nebo @State — strategie skrývá detaily implementace před View. Například strategie validace textu (emailValidator, phoneValidator) se mění podle typu vstupního pole. Kombinace Strategy se SwiftUI poskytuje flexibilitu bez dědičnosti od UIKit.
Strategy v Kotlin používá Interface na úrovni jazyka a funkcionální rozhraní (SAM) pro zjednodušení. Kotlin podporuje lambdy, což umožňuje předávat algoritmy jako funkce bez deklarace samostatné třídy strategie. V Android se vzor aplikuje ve ViewModel a Use Cases pro izolaci algoritmů načítání dat, ukládání do mezipaměti a zpracování chyb. Android projekty s Clean Architecture používají Strategy pro injektování různých implementací repository podle příznaků (mock, real, cache).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Konkrétní strategie
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Bankovní API přes 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)
}
}
// Použití ve ViewModel
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Zpracování výsledku
}
}
}
Strategy s Hilt/Dagger — v Android projektech jsou strategie často injektovány přes DI. Hilt poskytuje konkrétní implementaci PaymentStrategy přes @Binds nebo @Provides. To umožňuje měnit strategii bez změny kódu kontextu — stačí změnit DI modul pro jiné sestavení (debug/release). Například pro ladění se injektuje MockPaymentStrategy, pro produkci — skutečná bankovní strategie. Kombinace Strategy + DI poskytuje maximální flexibilitu.
Strategy vs State — strukturálně jsou vzory identické: oba používají kompozici s rozhraním a konkrétními třídami. Rozdíl je v účelu: Strategy vybírá nezávislý algoritmus, State řídí chování objektu podle jeho stavu. Ve State kontext sám mění strategii při změně stavu, v Strategy kontext neřídí přepínání — klient explicitně určuje algoritmus. Strategie o sobě navzájem nevědí, stavy do sebe mohou přecházet.
Strategy vs Command — Command zapouzdřuje jedinou akci jako objekt, Strategy zapouzdřuje sadu zaměnitelných algoritmů. Command — «co udělat» (jedno volání execute), Strategy — «jak udělat» (algoritmus z několika kroků). Command se používá pro fronty, odložené provedení, undo/redo. Strategy — pro výběr způsobu provedení úkolu za běhu. Příkazy lze parametrizovat strategiemi, kombinací obou vzorů.
| Charakteristika | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Účel | Zaměnitelné algoritmy | Chování podle stavu | Zapouzdření požadavku | Kostra algoritmu |
| Změna | Explicitně klientem | Automaticky kontextem | Klientem nebo frontou | Dědičností |
| Úroveň | Objektová (kompozice) | Objektová (kompozice) | Objektová | Třídní (dědičnost) |
Strategy vs Template Method — oba vzory definují algoritmy, ale různými způsoby. Template Method používá dědičnost: základní třída definuje kostru algoritmu (šablonovou metodu), podtřídy přepisují jednotlivé kroky. Strategy používá kompozici: algoritmus je zcela vyčleněn do samostatné třídy. Template Method je jednodušší pro případy s pevnou strukturou algoritmu, Strategy — když jsou algoritmy zcela odlišné a mohou se dynamicky měnit.
Zpracování plateb — klasický příklad Strategy. Nákupní košík internetového obchodu obsahuje seznam produktů a způsob platby volí uživatel. Každý způsob (karta, PayPal, Apple Pay, Google Pay, kryptoměna) — samostatná strategie se společným podpisem pay(amount). Kontext PaymentProcessor neví, jak přesně se platba provádí — volá společnou metodu. Přidání nového způsobu platby nevyžaduje změnu kódu košíku.
Validace dat — Strategy se aplikuje pro různá pravidla validace stejného pole. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implementují společné rozhraní ValidationStrategy s metodou validate(input). Registrační formulář používá sadu strategií pro kontrolu každého pole. Validační strategie lze kombinovat do řetězce (Chain of Responsibility) nebo aplikovat všechny najednou v cyklu. To nahrazuje dlouhé kontroly if-else kolekcí polymorfních validátorů.
// Strategie třídění
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) }
}
Autentizace — v mobilních aplikacích se autentizační strategie přepínají podle poskytovatele. AuthStrategy s metodami login(), logout(), getToken() je implementována pro EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. Kontext AuthManager přijímá strategii přes DI nebo továrnu. To umožňuje přidávat nové poskytovatele autentizace bez změny přihlašovací obrazovky. Vzor Strategy — základ mnoha OAuth knihoven a Firebase Authentication.
Často kladené otázky
Strategy je opodstatněný, když máte 3+ algoritmů, které se mohou měnit nebo rozšiřovat. Pokud jsou algoritmy 2 a stabilní — jednoduchý if-else je méně náročný. Použijte Strategy, když se algoritmy používají v různých částech aplikace, když je potřeba nahrazovat algoritmy za běhu nebo když každý algoritmus vyžaduje vlastní závislosti a testy.
Ne, jsou to různé vzory s podobnou strukturou. Strategy — klient explicitně vybírá algoritmus a strategie jsou nezávislé. State — objekt sám mění své chování při změně vnitřního stavu a stavy mohou do sebe přecházet. Ve State kontext řídí změnu stavu, v Strategy — klientský kód.
Ano, ve Swift a Kotlin lze strategii předat jako closure nebo lambdu. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. To zjednodušuje kód pro jednoduché případy, ale ztrácí se pojmenování a dokumentace. Pro 1-2 algoritmy — closure stačí, pro 4+ — jsou lepší samostatné třídy.
Každá strategie se testuje samostatným unit testem s mock závislostmi. Kontext se testuje s mock strategií — ověřuje se, že kontext volá metodu strategie a předává správné parametry. Ve Swift použijte XCTest + protokoly pro mock, v Kotlin — MockK nebo Mockito. Hlavní výhoda: každá strategie se testuje izolovaně bez složité konfigurace.
Ano, Strategy (Strategie) — jeden z 23 vzorů popsaných v knize «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994). Patří do skupiny behaviorálních vzorů (Behavioral Patterns). Synonyma: Policy (Politika). Původní příkladový kód v Smalltalk-80 je dostupný v původním vydání GoF.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také