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 (استراتژی مشخص) رابط را پیادهسازی میکند و الگوریتم مشخص را شامل میشود. کلاینت استراتژی مورد نیاز را ایجاد میکند و آن را از طریق سازنده، setter یا پارامتر متد به زمینه منتقل میکند. زمینه نمیداند کدام استراتژی اجرا میشود — فقط با رابط کار میکند.
| جزء | نقش | مثال |
|---|---|---|
| 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 زمانی موجه است که ۳+ الگوریتم داشته باشید که ممکن است تغییر کنند یا گسترش یابند. اگر ۲ الگوریتم دارید و پایدار هستند — if-else ساده هزینه کمتری دارد. از Strategy استفاده کنید وقتی الگوریتمها در بخشهای مختلف برنامه استفاده میشوند، وقتی باید الگوریتمها را در زمان اجرا جایگزین کنید یا وقتی هر الگوریتم نیاز به وابستگیها و تستهای خاص خود دارد.
خیر، اینها الگوهای متفاوتی با ساختار مشابه هستند. Strategy — کلاینت به صراحت الگوریتم را انتخاب میکند و استراتژیها مستقل هستند. State — شیء خود رفتارش را با تغییر وضعیت داخلی تغییر میدهد و وضعیتها میتوانند به یکدیگر تغییر کنند. در State زمینه تغییر وضعیت را مدیریت میکند، در Strategy — کد کلاینت.
بله، در Swift و Kotlin استراتژی را میتوان به عنوان closure یا لامبدا منتقل کرد. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. این کار کد را برای موارد ساده سادهتر میکند، اما نامگذاری و مستندات از دست میروند. برای ۱-۲ الگوریتم — closure کافی است، برای ۴+ — کلاسهای جداگانه بهتر است.
هر استراتژی با یک تست واحد جداگانه با وابستگیهای mock تست میشود. زمینه با استراتژی mock تست میشود — بررسی میشود که زمینه متد استراتژی را فراخوانی میکند و پارامترهای صحیح را منتقل میکند. در Swift از XCTest + پروتکلها برای mock استفاده کنید، در Kotlin — MockK یا Mockito. مزیت اصلی: هر استراتژی به صورت ایزوله بدون پیکربندی پیچیده تست میشود.
بله، Strategy (استراتژی) — یکی از ۲۳ الگویی است که در کتاب «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994) توضیح داده شده است. به گروه الگوهای رفتاری (Behavioral Patterns) تعلق دارد. نامهای دیگر: Policy (سیاست). کد مثال اصلی به زبان Smalltalk-80 در نسخه اصلی GoF موجود است.
نتایج
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.