Strategy (Strateji), birbirinin yerine geçebilen algoritma ailesini tanımlayan ve her birini ayrı bir sınıfa (Strategy) yerleştiren davranışsal bir tasarım desenidir. Desen, algoritmayı anında seçmeye olanak tanır: istemci kodu ortak bir Strategy arabirimi üzerinden çalışır ve somut uygulama çalışma zamanında değiştirilir. iOS'ta desen, Protocol + strateji sınıfları aracılığıyla, Android'de ise Interface + uygulamalar aracılığıyla gerçekleştirilir. Strategy, 23 GoF deseninden biridir ve ödeme işleme, doğrulama, sıralama ve veri filtreleme için yaygın olarak kullanılır. Daha fazla ayrıntı için — orijinal GoF açıklamasına bakın.
Önemli Noktalar
Strategy, 23 GoF (Gang of Four) deseninden biridir ve "Design Patterns: Elements of Reusable Object-Oriented Software" (1994) kitabında açıklanmıştır. Desen, çalışma zamanında algoritma seçme sorununu çözer. Birçok koşullu ifadeye (if-else, switch) sahip tek bir sınıf yazmak yerine, Strategy her algoritmayı ortak bir arabirime sahip ayrı bir sınıfa çıkarmayı önerir. Bağlam (stratejiyi kullanan sınıf) Strategy arabirimine bir referans tutar ve yürütmeyi somut stratejiye devreder.
Desenin yapısı üç öğe içerir: Context (bağlam) Strategy'ye bir referans tutar ve yöntemini çağırır; Strategy (arabirim) tüm algoritmalar için ortak bir yöntem bildirir; ConcreteStrategy (somut strateji) arabirimi uygular ve somut algoritmayı içerir. İstemci istenen stratejiyi oluşturur ve bunu bir kurucu, ayarlayıcı veya yöntem parametresi aracılığıyla bağlama iletir. Bağlam, hangi belirli stratejinin yürütüldüğünü bilmez — yalnızca arabirimle çalışır.
| Bileşen | Rol | Örnek |
|---|---|---|
| Context | Strategy'ye referans tutar | PaymentProcessor, Sorter |
| Strategy | Algoritmalar için ortak arabirim | Protocol PaymentStrategy |
| ConcreteStrategy | Algoritmanın somut uygulaması | CardPayment, PayPalPayment |
Open/Closed İlkesi — Strategy'nin ana avantajı. Sistem genişletmeye açıktır (yeni strateji eklenebilir) ve değişikliğe kapalıdır (bağlam kodunun değişmesi gerekmez). Desen olmadan, yeni bir algoritma eklemek mevcut sınıfın değiştirilmesini gerektirir, bu da OCP'yi ihlal eder ve gerileme hataları riskini artırır. Strategy ayrıca sınıf boyutunu da azaltır: switch-case içeren 200 satırlık bir sınıf yerine, her biri 20 satırlık 6 sınıf elde edersiniz.
Swift'te Strategy, Protocol (strateji arabirimi) ve strateji sınıfları veya yapıları aracılığıyla uygulanır. Swift protokolleri ilişkili türleri ve genel kısıtlamaları destekleyerek stratejiler tasarlarken esneklik sağlar. Bağlam genellikle stratejiyi init'te veya bir özellik aracılığıyla kabul eden bir ViewModel sınıfı veya hizmetidir. Desen, iOS projelerinde olay işleme, animasyonlar, veri biçimlendirme ve UI stratejileri için yaygın olarak kullanılır.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Concrete Strategies
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Banka API'sine istek gönderme
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// PayPal SDK'ya yönlendirme
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)
}
}
// Kullanım
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
SwiftUI'da Strategy — desen MVVM ile doğal olarak bütünleşir. Bir ViewModel bir strateji özelliği içerir ve kullanıcı eyleminde yöntemini çağırır. SwiftUI View, @Published veya @State aracılığıyla veri alır — strateji, uygulama ayrıntılarını View'dan gizler. Örneğin, bir metin doğrulama stratejisi (emailValidator, phoneValidator) giriş alanı türüne göre değiştirilir. Strategy'yi SwiftUI ile birleştirmek, UIKit'ten miras almadan esneklik sağlar.
Kotlin'de Strategy dil düzeyinde Interface kullanır ve basitleştirme için işlevsel arabirimler (SAM) kullanır. Kotlin lambda'yı destekleyerek ayrı bir strateji sınıfı bildirmeden algoritmaların işlev olarak iletilmesine olanak tanır. Android'de desen, ViewModel ve Use Cases'de veri yükleme, önbelleğe alma ve hata işleme algoritmalarını yalıtmak için kullanılır. Clean Architecture kullanan Android projeleri, bayraklara (mock, real, cache) bağlı olarak farklı depo uygulamaları enjekte etmek için Strategy kullanır.
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Concrete Strategies
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Retrofit ile banka API'si
return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
}
}
class PayPalPaymentStrategy(
private val email: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// PayPal SDK entegrasyonu
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'de kullanım
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Sonuç işleme
}
}
}
Hilt/Dagger ile Strategy — Android projelerinde stratejiler genellikle DI aracılığıyla enjekte edilir. Hilt, @Binds veya @Provides aracılığıyla PaymentStrategy'nin somut bir uygulamasını sağlar. Bu, bağlam kodunu değiştirmeden stratejinin değiştirilmesine olanak tanır — başka bir derleme (debug/release) için yalnızca DI modülünü değiştirin. Örneğin, hata ayıklama için MockPaymentStrategy, üretim için gerçek bir bankacılık stratejisi enjekte edilir. Strategy + DI kombinasyonu maksimum esneklik sağlar.
Strategy vs State — yapısal olarak desenler aynıdır: her ikisi de bir arabirim ve somut sınıflarla bileşim kullanır. Fark amaçtadır: Strategy bağımsız bir algoritma seçer, State nesnenin durumuna bağlı olarak davranışını kontrol eder. State'te, bağlam durum değiştiğinde stratejiyi kendisi değiştirir; Strategy'de, bağlam değiştirmeyi kontrol etmez — istemci algoritmayı açıkça belirler. Stratejiler birbirini bilmezken, durumlar birbirine geçiş yapabilir.
Strategy vs Command — Command tek bir eylemi nesne olarak kapsüller, Strategy bir dizi değiştirilebilir algoritmayı kapsüller. Command "ne yapılacağı" (tek bir execute çağrısı), Strategy "nasıl yapılacağı" (birden çok adımlı algoritma). Command kuyruklar, gecikmeli yürütme, geri al/yinele için kullanılır. Strategy, çalışma zamanında bir görevi gerçekleştirme yolunu seçmek için kullanılır. Komutlar, her iki deseni birleştirerek stratejilerle parametrelendirilebilir.
| Özellik | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Amaç | Değiştirilebilir algoritmalar | Duruma göre davranış | İstek kapsülleme | Algoritma iskeleti |
| Değiştirme | İstemci tarafından açıkça | Bağlam tarafından otomatik | İstemci veya kuyruk | Kalıtım yoluyla |
| Seviye | Nesne (bileşim) | Nesne (bileşim) | Nesne | Sınıf (kalıtım) |
Strategy vs Template Method — her iki desen de algoritmaları tanımlar ancak farklı şekillerde. Template Method kalıtım kullanır: bir temel sınıf algoritma iskeletini (şablon yöntemi) tanımlar, alt sınıflar tek tek adımları geçersiz kılar. Strategy bileşim kullanır: algoritma tamamen ayrı bir sınıfa dışsallaştırılır. Template Method sabit algoritma yapısına sahip durumlar için daha basittir, Strategy — algoritmalar tamamen farklı olduğunda ve dinamik olarak değişebildiğinde.
Ödeme İşleme — Strategy'nin klasik örneği. Çevrimiçi mağazanın alışveriş sepeti bir ürün listesi içerir ve ödeme yöntemi kullanıcı tarafından seçilir. Her yöntem (kart, PayPal, Apple Pay, Google Pay, kripto para) ortak bir pay(amount) imzasına sahip ayrı bir stratejidir. PaymentProcessor bağlamı, ödemenin tam olarak nasıl işlendiğini bilmez — ortak yöntemi çağırır. Yeni bir ödeme yöntemi eklemek, sepet kodunun değiştirilmesini gerektirmez.
Veri Doğrulama — Strategy, aynı alanın farklı doğrulama kuralları için kullanılır. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy, validate(input) yöntemiyle ortak bir ValidationStrategy arabirimi uygular. Kayıt formu, her alanı kontrol etmek için bir dizi strateji kullanır. Doğrulama stratejileri bir zincirde (Chain of Responsibility) birleştirilebilir veya bir döngüde hepsi birden uygulanabilir. Bu, uzun if-else kontrollerini polimorfik doğrulayıcı koleksiyonuyla değiştirir.
// Sıralama stratejisi
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) }
}
Kimlik Doğrulama — mobil uygulamalarda, kimlik doğrulama stratejileri sağlayıcıya göre değiştirilir. AuthStrategy, login(), logout(), getToken() yöntemleriyle EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth için uygulanır. AuthManager bağlamı, stratejiyi DI veya fabrika aracılığıyla kabul eder. Bu, giriş ekranını değiştirmeden yeni kimlik doğrulama sağlayıcıları eklemeye olanak tanır. Strategy deseni, birçok OAuth kütüphanesinin ve Firebase Authentication'un temelidir.
Sıkça Sorulan Sorular
Strategy, değişebilecek veya genişleyebilecek 3+ algoritmanız olduğunda haklıdır. 2 algoritma varsa ve stabillerse — basit bir if-else daha az maliyetlidir. Algoritmalar uygulamanın farklı bölümlerinde kullanıldığında, algoritmaları çalışma zamanında değiştirmeniz gerektiğinde veya her algoritma kendi bağımlılıklarını ve testlerini gerektirdiğinde Strategy kullanın.
Hayır, bunlar benzer yapıya sahip farklı desenlerdir. Strategy — istemci algoritmayı açıkça seçer ve stratejiler bağımsızdır. State — iç durumu değiştiğinde nesnenin kendisi davranışını değiştirir ve durumlar birbirine geçiş yapabilir. State'te bağlam durum değişikliklerini yönetir; Strategy'de bunu istemci kodu yapar.
Evet, Swift ve Kotlin'de bir strateji closure veya lambda olarak iletilebilir. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Bu, basit durumlar için kodu basitleştirir ancak adlandırma ve belgelendirmeyi kaybettirir. 1-2 algoritma için bir closure yeterlidir; 4+ için ayrı sınıflar daha iyidir.
Her strateji, sahte bağımlılıklar kullanılarak ayrı bir birim testiyle test edilir. Bağlam, sahte bir stratejiyle test edilir — bağlamın strateji yöntemini çağırdığı ve doğru parametreleri ilettiği doğrulanır. Swift'te sahte için XCTest + protokoller kullanın; Kotlin'de MockK veya Mockito kullanın. Ana avantaj: her strateji karmaşık kurulum olmadan izole olarak test edilir.
Evet, Strategy, "Design Patterns: Elements of Reusable Object-Oriented Software" (Gamma, Helm, Johnson, Vlissides, 1994) kitabında açıklanan 23 desenden biridir. Davranışsal desenler grubuna aittir. Diğer adı: Policy. Smalltalk-80'deki orijinal örnek kod, GoF'nin orijinal baskısında mevcuttur.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun