نمط Strategy
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 69 من 87.
يُعد نمط الاستراتيجية (Strategy Pattern) نمط تصميم سلوكي يتيح لك تعريف عائلة من الخوارزميات، وتغليف كل منها في فئة (class) خاصة بها، وجعلها قابلة للتبديل فيما بينها. فبدلاً من كتابة سلوك معين بشكل ثابت (hardcoding)، يمكنك تبديل الخوارزميات في وقت التشغيل (runtime) دون تغيير الكود الذي يستخدمها.
تخيل نظام دفع يدعم طرق دفع متعددة. بدون نمط الاستراتيجية (Strategy Pattern)، ستنتهي بمنطق شرطي فوضوي. باستخدامه، تصبح كل طريقة دفع استراتيجية خاصة بها:
interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " via Credit Card");
}
}
class PayPalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " via PayPal");
}
}تحتفظ فئة context بمرجع لاستراتيجية معينة وتفوض العمل إليها. النقطة الأساسية هي أن السياق لا يعرف أي استراتيجية ملموسة يستخدمها—فهو يعرف الواجهة فقط:
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}الآن يمكنك تغيير سلوك الدفع ديناميكيًا:
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100); // تم دفع 100 عبر بطاقة الائتمان
cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(50); // تم دفع 50 عبر PayPalيتألق نمط الاستراتيجية (Strategy Pattern) عندما يكون لديك طرق متعددة لتنفيذ إجراء ما وتريد التبديل بينها بسهولة. إن إضافة وسيلة دفع جديدة تعني ببساطة إنشاء فئة (class) جديدة تقوم بتنفيذ PaymentStrategy—دون الحاجة إلى إجراء أي تغييرات على الكود الحالي.
التحدي
سهللنقم ببناء نظام لتنسيق النصوص باستخدام نمط الاستراتيجية (Strategy Pattern)! ستقوم بإنشاء نظام حيث يمكن تبديل استراتيجيات التنسيق المختلفة في وقت التشغيل لتحويل النص بطرق متنوعة — وهو أمر مثالي لتطبيق يحتاج إلى دعم تنسيقات مخرجات متعددة.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
TextFormatter.java: قم بتعريف واجهةTextFormatterالتي ستنفذها جميع استراتيجيات التنسيق. يجب أن تصرح عن طريقة واحدةString format(String text)تأخذ سلسلة نصية وتعيد النسخة المنسقة.Formatters.java: قم بإنشاء ثلاث استراتيجيات ملموسة (concrete strategies) تنفذ واجهةTextFormatterالخاصة بك:UpperCaseFormatter- يحول النص إلى أحرف كبيرة بالكامل.LowerCaseFormatter- يحول النص إلى أحرف صغيرة بالكامل.TitleCaseFormatter- يجعل الحرف الأول من كل كلمة كبيراً بينما يجعل باقي الأحرف صغيرة. الكلمات مفصولة بمسافات.TextEditor.java: قم بإنشاء فئة السياق (context class) التي تستخدم استراتيجية التنسيق. يجب أن يحتويTextEditorالخاص بك على:حقل خاص (private field) للاحتفاظ باستراتيجية
TextFormatterالحالية.طريقة
setFormatter(TextFormatter formatter)تقوم بتغيير استراتيجية التنسيق النشطة.طريقة
publishText(String text)تقوم بتطبيق المنسق الحالي على النص وتطبعPublished: [formatted text]. إذا لم يتم تعيين أي منسق، يجب أن تطبعPublished: [original text]دون تغيير.Main.java: اجمع نظام الاستراتيجية الخاص بك معاً! ستتلقى مدخلين: النص المراد تنسيقه (String) ونمط التنسيق (String:"upper"، أو"lower"، أو"title").قم بإنشاء
TextEditorواستدعِ أولاًpublishTextمع نص الإدخال الخاص بك قبل تعيين أي منسق (لإظهار السلوك الافتراضي).بعد ذلك، بناءً على نمط الإدخال، قم بتعيين استراتيجية المنسق المناسبة واستدعِ
publishTextمرة أخرى بنفس النص لإظهار النتيجة المنسقة.
ستتلقى مدخلين بالترتيب: النص المراد تنسيقه (String) ونمط التنسيق (String).
على سبيل المثال، مع المدخلات hello world و title، سيكون مخرجك:
Published: hello world
Published: Hello Worldلاحظ كيف أن TextEditor لا يعرف أي شيء عن منطق التنسيق المحدد — فهو ببساطة يفوض المهمة إلى أي استراتيجية تم تعيينها حالياً. وهذا يجعل من السهل جداً إضافة أنماط تنسيق جديدة دون المساس بكود المحرر!
ورقة مرجعية
يُعد نمط الاستراتيجية (Strategy Pattern) نمط تصميم سلوكي يحدد عائلة من الخوارزميات، ويغلف كل واحدة منها في فئتها الخاصة، ويجعلها قابلة للتبديل. فهو يسمح بتبديل الخوارزميات في وقت التشغيل دون تغيير الكود الذي يستخدمها.
المكونات الرئيسية
واجهة الاستراتيجية (Strategy Interface): تحدد الواجهة المشتركة لجميع الاستراتيجيات الملموسة.
interface PaymentStrategy {
void pay(int amount);
}الاستراتيجيات الملموسة (Concrete Strategies): تقوم بتنفيذ واجهة الاستراتيجية بخوارزميات محددة.
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " via Credit Card");
}
}
class PayPalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " via PayPal");
}
}فئة السياق (Context Class): تحتفظ بمرجع للاستراتيجية وتفوض العمل إليها. يعرف السياق الواجهة فقط، وليس التنفيذ الملموس.
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}الاستخدام
يمكن تغيير الاستراتيجيات ديناميكيًا في وقت التشغيل:
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100); // تم دفع 100 عبر بطاقة الائتمان
cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(50); // تم دفع 50 عبر PayPalالفوائد
يُعد نمط الاستراتيجية مثاليًا عندما يكون لديك طرق متعددة لأداء إجراء ما وتريد التبديل بينها بسهولة. إضافة استراتيجية جديدة تعني ببساطة إنشاء فئة جديدة تنفذ واجهة الاستراتيجية - دون الحاجة إلى إجراء تغييرات على الكود الحالي.
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String text = scanner.nextLine();
String style = scanner.nextLine();
// TODO: قم بإنشاء مثيل (instance) لـ TextEditor
// TODO: استدعِ publishText مع النص المدخل (قبل تعيين أي منسق)
// TODO: بناءً على مدخل النمط ("upper" أو "lower" أو "title")،
// قم بتعيين المنسق المناسب واستدعِ publishText مرة أخرى
}
}يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر