نمط الاستراتيجية
نمط الاستراتيجية
نمط الاستراتيجية (Strategy) هو أحد أكثر الأنماط السلوكية توظيفًا في مشاريع Java الاحترافية. فكرته الجوهرية بسيطة: عرّف عائلةً من الخوارزميات، وأخفِ كل خوارزمية خلف واجهة مشتركة، واجعلها قابلة للتبادل في وقت التشغيل — دون الحاجة إلى تعديل المكوّنات التي تستخدمها. يُجسّد النمط مبدأ الفتح/الإغلاق مباشرةً: الكود مفتوح للتوسعة (أضف خوارزمية جديدة) ومغلق للتعديل (الكود القائم لا يتغيّر).
المشكلة في غياب Strategy
تخيّل وحدة دفع تدعم بطاقات الائتمان وPayPal والتحويل البنكي. بدون Strategy، تضع المسوّدة الأولى كل المنطق في كلاس واحد بسلسلة if/else أو switch:
كل طريقة دفع جديدة تفرض تغييرًا داخل PaymentProcessor، ويستحيل اختبار الخوارزميات بمعزل عن بعضها، وينمو الكلاس بلا حدود. يحلّ Strategy هذه المشكلات الثلاث.
بنية النمط
يتكوّن النمط من ثلاثة أدوار:
- واجهة Strategy — العقد الذي يجب أن تلتزم به كل خوارزمية.
- الاستراتيجيات الملموسة — كلاس لكل خوارزمية يُنفّذ الواجهة.
- السياق (Context) — يحمل مرجعًا لاستراتيجية ما، يفوّض العمل إليها، ويستطيع تبديلها في وقت التشغيل.
مثال Java كنوني — معالجة المدفوعات
الاستخدام واضح ولا يحتوي على أي تفريع شرطي داخل PaymentContext:
Lambda كاستراتيجية (Java 8+)
لاحظ تعليق @FunctionalInterface على PaymentStrategy — لها تحديدًا دالة مجردة واحدة. هذا يجعل أي تعبير lambda استراتيجيةً ملموسة صالحة، ممّا يُغني عن كتابة كلاسات منفصلة للخوارزميات البسيطة:
مراجع الدوال (method references) تعمل بنفس الأسلوب حين تمتلك دالة قائمة مسبقًا التوقيع الصحيح:
cardNumber)، أو تمتلك حالة قابلة للتغيّر، أو تحتاج لاختبار وحدة مستقل بالاسم.
مثال حقيقي — الترتيب بـ Comparator
أنت تستخدم Strategy يوميًا بالفعل. java.util.Comparator هي واجهة Strategy. Collections.sort() وList.sort() هما السياق:
خط الـ stream (السياق) لا يتغيّر؛ فقط Comparator (الاستراتيجية) يختلف. طبّق مصمّمو JDK هذا النمط حتى لا تضطر أبدًا لتعديل List لإضافة ترتيب جديد.
Strategy مقارنةً بأنماط مشابهة
- Template Method — يُثبّت الهيكل في كلاس أساسي ويترك الخطوات للوراثة. Strategy يفوّض الخوارزمية كاملةً عبر التركيب. الأفضل دومًا التركيب على الوراثة.
- State — يبدو متطابقًا في الكود (واجهة + سياق بمرجع)، لكن State ينتقل من تلقاء نفسه؛ Strategy تُبدَّل بواسطة الكود الاستدعائي. إن قرّر الكائن أي استراتيجية يستخدم تاليًا فهو State.
- Command — يُغلّف طلبًا ككائن وكثيرًا ما يدعم التراجع. Strategy يُغلّف خوارزمية؛ والنمطان يُدمجان كثيرًا (Command يختار من بين Strategies).
المقايضات ومتى لا تستخدم النمط
النمط قوي لكنه ليس بلا ثمن:
- العملاء بحاجة لمعرفة الاستراتيجيات. المنادي يحتاج أن يعرف أي استراتيجية يحقن. إن كان منطق الاختيار معقدًا، انقله إلى مصنع أو طبقة تهيئة لتحافظ على نظافة العميل.
- مبالغة لخيارَين فقط. إن كان علمٌ منطقي (boolean) بين فرعَين بسيطَين مستقرًّا، تضيف هرمية Strategy تعقيدًا دون فائدة. طبّقه حين تتوقع عائلةً متنامية من الخوارزميات أو حاجةً لتبديل وقت تشغيل.
- انفجار الواجهات. كل عائلة خوارزميات متمايزة تحتاج واجهتها الخاصة. احتفظ بحجم مناسب — واجهة واحدة لكل محور تباين.
switch عادي قد يكون أوضح.
الخلاصة
يُغلّف نمط Strategy الخوارزميات القابلة للتبادل خلف واجهة مشتركة، مما يتيح تبديل السلوك في وقت التشغيل دون تعديل السياق. في Java الحديثة، تحوّل @FunctionalInterface أي Strategy إلى هدف للـ lambdas ومراجع الدوال، مما يُقلّل الكود الزائد تقليلًا كبيرًا. تراه في كل مكان في JDK — Comparator وPredicate وRunnable وExecutorService كلها Strategy في العمل. طبّقه حين يكون لديك عائلة خوارزميات تتباين مستقلةً عن الكود الذي يستخدمها.