Strategy (الاستراتيجية) هو نمط تصميم سلوكي يُعرّف مجموعة من الخوارزميات القابلة للتبديل ويضع كلًا منها في فئة منفصلة (Strategy). يسمح النمط باختيار خوارزمية أثناء التنفيذ: يعمل كود العميل من خلال واجهة Strategy مشتركة، ويتم استبدال التنفيذ المحدد في وقت التشغيل. في iOS، يتم تنفيذ النمط عبر Protocol + فئات الاستراتيجية، في Android — عبر Interface + تطبيقات. Strategy هو أحد أنماط GoF الـ 23، ويُستخدم على نطاق واسع لمعالجة المدفوعات والتحقق والفرز وتصفية البيانات. للمزيد من التفاصيل — راجع الوصف الأصلي لـ GoF.
النقاط الرئيسية
Strategy هو أحد أنماط GoF الـ 23 (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 تدعم الأنواع المرتبطة والقيود العامة، مما يوفر مرونة عند تصميم الاستراتيجيات. السياق عادةً ما يكون فئة ViewModel أو خدمة تقبل الاستراتيجية في init أو عبر خاصية. يُستخدم النمط على نطاق واسع في مشاريع iOS لمعالجة الأحداث والرسوم المتحركة وتنسيق البيانات واستراتيجيات UI.
// 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 {
// إرسال طلب إلى 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 على خاصية استراتيجية ويستدعي طريقتها عند إجراء المستخدم. View في SwiftUI تستقبل البيانات عبر @Published أو @State — تخفي الاستراتيجية تفاصيل التنفيذ عن View. على سبيل المثال، يتم تبديل استراتيجية التحقق من النص (emailValidator, phoneValidator) حسب نوع حقل الإدخال. دمج Strategy مع SwiftUI يوفر مرونة دون الوراثة من UIKit.
Strategy في Kotlin يستخدم Interface على مستوى اللغة والواجهات الوظيفية (SAM) للتبسيط. Kotlin يدعم lambda، مما يسمح بتمرير الخوارزميات كدوال دون الإعلان عن فئة استراتيجية منفصلة. في Android، يُستخدم النمط في ViewModel و Use Cases لعزل خوارزميات تحميل البيانات والتخزين المؤقت ومعالجة الأخطاء. مشاريع Android مع Clean Architecture تستخدم Strategy لحقن تطبيقات مستودع مختلفة حسب الأعلام (mock, real, cache).
// 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 {
// API البنك عبر Retrofit
return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
}
}
class PayPalPaymentStrategy(
private val email: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// تكامل SDK لـ PayPal
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 يُستخدم لقوائم الانتظار والتنفيذ المؤجل والتراجع/الإعادة. 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+ خوارزميات قد تتغير أو تتوسع. إذا كان هناك خوارزميتان وهما مستقرتان — if-else بسيط أقل تكلفة. استخدم Strategy عندما تُستخدم الخوارزميات في أجزاء مختلفة من التطبيق، عندما تحتاج إلى تبديل الخوارزميات في وقت التشغيل، أو عندما تتطلب كل خوارزمية تبعيات واختبارات خاصة بها.
لا، هما نمطان مختلفان ببنية متشابهة. Strategy — العميل يختار الخوارزمية صراحةً، والاستراتيجيات مستقلة. State — الكائن نفسه يغير سلوكه عندما تتغير حالته الداخلية، ويمكن للحالات أن تنتقل بين بعضها. في State، السياق يدير تغييرات الحالة؛ في Strategy، كود العميل هو من يفعل ذلك.
نعم، في Swift و Kotlin يمكن تمرير استراتيجية كـ closure أو lambda. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. هذا يبسط الكود للحالات البسيطة ولكنه يفقد التسمية والتوثيق. لـ 1-2 خوارزميات، يكفي closure؛ لـ 4+، من الأفضل استخدام فئات منفصلة.
يتم اختبار كل استراتيجية باختبار وحدة منفصل باستخدام تبعيات mock. يتم اختبار السياق باستراتيجية mock — للتحقق من أن السياق يستدعي طريقة الاستراتيجية ويمرر المعاملات الصحيحة. في Swift، استخدم XCTest + بروتوكولات لـ mock؛ في Kotlin، استخدم MockK أو Mockito. الميزة الرئيسية: كل استراتيجية تُختبر بشكل منعزل دون إعداد معقد.
نعم، Strategy هو أحد الأنماط الـ 23 الموصوفة في كتاب "Design Patterns: Elements of Reusable Object-Oriented Software" (Gamma، Helm، Johnson، Vlissides، 1994). ينتمي إلى مجموعة الأنماط السلوكية. أسماء بديلة: Policy. كود المثال الأصلي بلغة Smalltalk-80 متاح في النسخة الأصلية من GoF.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.