Strategy (Стратегија) — бихејвиорални образац пројектовања који дефинише породицу заменљивих алгоритама и смешта сваки од њих у посебну класу (Strategy). Образац омогућава избор алгоритма у лету: клијентски код ради преко заједничког интерфејса Strategy, а конкретна имплементација се подмеће у рантајму. У iOS-у образац се имплементира кроз Protocol + класе-стратегије, у Android-у — кроз Interface + имплементације. Strategy — један од 23 GoF образаца, широко се примењује за обраду плаћања, валидацију, сортирање и филтрирање података. Више — у оригиналном опису GoF.
Главно
Strategy — један од 23 GoF (Gang of Four) образаца, описан у књизи «Design Patterns: Elements of Reusable Object-Oriented Software» (1994). Образац решава проблем избора алгоритма у рантајму. Уместо писања једне класе са мноштвом условних оператора (if-else, switch), Strategy предлаже издвајање сваког алгоритма у посебну класу са заједничким интерфејсом. Контекст (класа која користи стратегију) чува референцу на интерфејс Strategy и делегира извршење конкретној стратегији.
Структура обрасца укључује три елемента: Context (контекст) садржи референцу на Strategy и позива њен метод; Strategy (интерфејс) декларише заједнички метод за све алгоритме; ConcreteStrategy (конкретна стратегија) имплементира интерфејс и садржи конкретан алгоритам. Клијент креира потребну стратегију и прослеђује је контексту кроз конструктор, сетер или параметар метода. Контекст не зна која стратегија се извршава — ради само са интерфејсом.
| Компонента | Улога | Пример |
|---|---|---|
| Context | Садржи референцу на Strategy | PaymentProcessor, Sorter |
| Strategy | Заједнички интерфејс за алгоритме | Protocol PaymentStrategy |
| ConcreteStrategy | Конкретна имплементација алгоритма | CardPayment, PayPalPayment |
Open/Closed принцип — главна предност Strategy. Систем је отворен за проширење (може се додати нова стратегија) и затворен за измену (не треба мењати код контекста). Без обрасца додавање новог алгоритма захтева измену постојеће класе, што крши OCP и повећава ризик од регресивних грешака. Strategy такође смањује величину класа: уместо 200-редне класе са switch-case добија се 6 класа од по 20 редова.
Strategy у Swift-у се имплементира кроз Protocol (интерфејс стратегије) и класе или структуре-стратегије. Swift протоколи подржавају associated types и generic constraints, што даје флексибилност при пројектовању стратегија. Контекст је обично класа ViewModel или сервис који прима стратегију у init или кроз својство. Образац се широко користи у iOS пројектима за обраду догађаја, анимација, форматирање података и UI стратегије.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Конкретне стратегије
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Слање захтева банкарском API-ју
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Преусмеравање на 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)
}
}
// Коришћење
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy у SwiftUI — образац се природно интегрише са MVVM. ViewModel садржи својство стратегије и позива њен метод при акцији корисника. SwiftUI View добија податке кроз @Published или @State — стратегија скрива детаље имплементације од View. На пример, стратегија валидације текста (emailValidator, phoneValidator) се мења у зависности од типа поља уноса. Комбиновање Strategy са SwiftUI даје флексибилност без наслеђивања од UIKit-а.
Strategy у Kotlin-у користи Interface на нивоу језика и функционалне интерфејсе (SAM) за поједностављење. Kotlin подржава ламбде, што омогућава прослеђивање алгоритама као функција без декларисања посебне класе стратегије. У Android-у образац се примењује у ViewModel и Use Cases за изолацију алгоритама учитавања података, кеширања и обраде грешака. Android пројекти са Clean Architecture користе Strategy за инјектовање различитих имплементација репозиторијума у зависности од флагова (mock, real, cache).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Конкретне стратегије
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Банкарски API кроз 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)
}
}
// Коришћење у ViewModel-у
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Обрада резултата
}
}
}
Strategy са Hilt/Dagger — у Android пројектима стратегије се често инјектују кроз DI. Hilt обезбеђује конкретну имплементацију PaymentStrategy кроз @Binds или @Provides. Ово омогућава промену стратегије без измене кода контекста — довољно је променити DI модул за другу компилацију (debug/release). На пример, за отклањање грешака инјектује се MockPaymentStrategy, за продукцију — реална банкарска стратегија. Комбинација Strategy + DI даје максималну флексибилност.
Strategy vs State — структурно су обрасци идентични: оба користе композицију са интерфејсом и конкретним класама. Разлика је у намени: Strategy бира независан алгоритам, State управља понашањем објекта у зависности од његовог стања. У State-у контекст сам мења стратегију при промени стања, у Strategy-у контекст не контролише пребацивање — клијент експлицитно задаје алгоритам. Стратегије не знају једна за другу, стања могу прелазити једна у друга.
Strategy vs Command — Command инкапсулира једну радњу као објекат, Strategy инкапсулира скуп заменљивих алгоритама. Command — «шта урадити» (један позив execute), Strategy — «како урадити» (алгоритам од више корака). Command се користи за редове, одложено извршење, undo/redo. Strategy — за избор начина извршења задатка у рантајму. Команде се могу параметризовати стратегијама, комбинујући оба обрасца.
| Карактеристика | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Намена | Заменљиви алгоритми | Понашање од стања | Инкапсулација захтева | Скелет алгоритма |
| Промена | Експлицитно од клијента | Аутоматски од контекста | Од клијента или реда | Наслеђивањем |
| Ниво | Објектни (композиција) | Објектни (композиција) | Објектни | Класни (наслеђивање) |
Strategy vs Template Method — оба обрасца дефинишу алгоритме, али на различите начине. Template Method користи наслеђивање: базна класа дефинише скелет алгоритма (шаблон метод), подкласе преписују појединачне кораке. Strategy користи композицију: алгоритам је у потпуности измештен у посебну класу. Template Method је једноставнији за случајеве са фиксном структуром алгоритма, Strategy — када су алгоритми потпуно различити и могу се динамички мењати.
Обрада плаћања — класичан пример Strategy. Корпа интернет продавнице садржи листу производа, а начин плаћања бира корисник. Сваки начин (картица, PayPal, Apple Pay, Google Pay, криптовалута) — посебна стратегија са заједничким потписом pay(amount). Контекст PaymentProcessor не зна како се тачно врши плаћање — позива заједнички метод. Додавање новог начина плаћања не захтева измену кода корпе.
Валидација података — Strategy се примењује за различита правила валидације истог поља. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy имплементирају заједнички интерфејс ValidationStrategy са методом validate(input). Формулар за регистрацију користи скуп стратегија за проверу сваког поља. Стратегије валидације се могу комбиновати у ланац (Chain of Responsibility) или примењивати све одједном у петљи. Ово замењује дугачке if-else провере колекцијом полиморфних валидатора.
// Стратегија сортирања
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) }
}
Аутентификација — у мобилним апликацијама стратегије аутентификације се пребацују у зависности од провајдера. AuthStrategy са методама login(), logout(), getToken() се имплементира за EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. Контекст AuthManager прима стратегију кроз DI или фабрику. Ово омогућава додавање нових провајдера аутентификације без измене екрана за пријаву. Образац Strategy — основа за многе OAuth библиотеке и Firebase Authentication.
Често постављана питања
Strategy је оправдан када имате 3+ алгоритма који се могу мењати или проширивати. Ако су алгоритма 2 и стабилни су — једноставан if-else је мање захтеван. Користите Strategy када се алгоритми користе у различитим деловима апликације, када треба подмењати алгоритме у рантајму или када сваки алгоритам захтева сопствене зависности и тестове.
Не, то су различити обрасци са сличном структуром. Strategy — клијент експлицитно бира алгоритам, и стратегије су независне. State — објекат сам мења своје понашање при промени унутрашњег стања, и стања могу прелазити једна у друга. У State-у контекст управља променом стања, у Strategy-у — клијентски код.
Да, у Swift-у и Kotlin-у стратегија се може проследити као затварање или ламбда. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Ово поједностављује код за једноставне случајеве, али се губи именовање и документација. За 1-2 алгоритма — closure је довољан, за 4+ — боље су посебне класе.
Свака стратегија се тестира посебним unit-тестом са mock-зависностима. Контекст се тестира са mock-стратегијом — проверава се да контекст позива метод стратегије и прослеђује исправне параметре. У Swift-у користите XCTest + протоколе за mock, у Kotlin-у — MockK или Mockito. Главна предност: свака стратегија се тестира изоловано без сложене конфигурације.
Да, Strategy (Стратегија) — један од 23 обрасца описаних у књизи «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994). Припада групи бихејвиоралних образаца (Behavioral Patterns). Синоними: Policy (Политика). Оригинални код примера на Smalltalk-80 доступан је у оригиналном издању GoF-а.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође